nautobot 2.3.13__py3-none-any.whl → 2.3.14__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 (295) hide show
  1. nautobot/circuits/tables.py +2 -1
  2. nautobot/core/filters.py +2 -0
  3. nautobot/core/jobs/cleanup.py +47 -11
  4. nautobot/core/templates/search.html +7 -0
  5. nautobot/core/testing/filters.py +20 -5
  6. nautobot/dcim/forms.py +6 -5
  7. nautobot/dcim/models/devices.py +1 -0
  8. nautobot/dcim/tests/test_forms.py +51 -2
  9. nautobot/extras/forms/mixins.py +10 -2
  10. nautobot/extras/jobs.py +6 -4
  11. nautobot/ipam/models.py +62 -11
  12. nautobot/ipam/tables.py +2 -2
  13. nautobot/ipam/tests/test_api.py +68 -1
  14. nautobot/ipam/tests/test_models.py +41 -0
  15. nautobot/project-static/docs/404.html +1 -1
  16. nautobot/project-static/docs/apps/index.html +1 -1
  17. nautobot/project-static/docs/apps/nautobot-apps.html +1 -1
  18. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +1 -1
  19. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +1 -1
  20. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1 -1
  21. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +1 -1
  22. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +1 -1
  23. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +1 -1
  24. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +1 -1
  25. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +1 -1
  26. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +1 -1
  27. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +1 -1
  28. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1 -1
  29. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1 -1
  30. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +1 -1
  31. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +7 -5
  32. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +1 -1
  33. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +1 -1
  34. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +1 -1
  35. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1 -1
  36. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +2 -2
  37. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1 -1
  38. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +1 -1
  39. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +1 -1
  40. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1 -1
  41. nautobot/project-static/docs/development/apps/api/configuration-view.html +1 -1
  42. nautobot/project-static/docs/development/apps/api/database-backend-config.html +1 -1
  43. nautobot/project-static/docs/development/apps/api/models/django-admin.html +1 -1
  44. nautobot/project-static/docs/development/apps/api/models/global-search.html +1 -1
  45. nautobot/project-static/docs/development/apps/api/models/graphql.html +1 -1
  46. nautobot/project-static/docs/development/apps/api/models/index.html +1 -1
  47. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +1 -1
  48. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +1 -1
  49. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +1 -1
  50. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +1 -1
  51. nautobot/project-static/docs/development/apps/api/platform-features/index.html +1 -1
  52. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +1 -1
  53. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +1 -1
  54. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +1 -1
  55. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +1 -1
  56. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +1 -1
  57. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +1 -1
  58. nautobot/project-static/docs/development/apps/api/prometheus.html +1 -1
  59. nautobot/project-static/docs/development/apps/api/setup.html +1 -1
  60. nautobot/project-static/docs/development/apps/api/testing.html +1 -1
  61. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +1 -1
  62. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +1 -1
  63. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +1 -1
  64. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +1 -1
  65. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +1 -1
  66. nautobot/project-static/docs/development/apps/api/views/base-template.html +1 -1
  67. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +1 -1
  68. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +1 -1
  69. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +1 -1
  70. nautobot/project-static/docs/development/apps/api/views/index.html +1 -1
  71. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +1 -1
  72. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +1 -1
  73. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +1 -1
  74. nautobot/project-static/docs/development/apps/api/views/notes.html +1 -1
  75. nautobot/project-static/docs/development/apps/api/views/rest-api.html +1 -1
  76. nautobot/project-static/docs/development/apps/api/views/urls.html +1 -1
  77. nautobot/project-static/docs/development/apps/index.html +1 -1
  78. nautobot/project-static/docs/development/apps/migration/code-updates.html +1 -1
  79. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +1 -1
  80. nautobot/project-static/docs/development/apps/migration/from-v1.html +1 -1
  81. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +1 -1
  82. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +1 -1
  83. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +1 -1
  84. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +1 -1
  85. nautobot/project-static/docs/development/apps/porting-from-netbox.html +1 -1
  86. nautobot/project-static/docs/development/core/application-registry.html +1 -1
  87. nautobot/project-static/docs/development/core/best-practices.html +1 -1
  88. nautobot/project-static/docs/development/core/bootstrap-ui.html +1 -1
  89. nautobot/project-static/docs/development/core/caching.html +1 -1
  90. nautobot/project-static/docs/development/core/controllers.html +1 -1
  91. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +1 -1
  92. nautobot/project-static/docs/development/core/generic-views.html +1 -1
  93. nautobot/project-static/docs/development/core/getting-started.html +154 -140
  94. nautobot/project-static/docs/development/core/homepage.html +1 -1
  95. nautobot/project-static/docs/development/core/index.html +1 -1
  96. nautobot/project-static/docs/development/core/model-checklist.html +1 -1
  97. nautobot/project-static/docs/development/core/model-features.html +1 -1
  98. nautobot/project-static/docs/development/core/natural-keys.html +1 -1
  99. nautobot/project-static/docs/development/core/navigation-menu.html +1 -1
  100. nautobot/project-static/docs/development/core/release-checklist.html +1 -1
  101. nautobot/project-static/docs/development/core/role-internals.html +1 -1
  102. nautobot/project-static/docs/development/core/settings.html +1 -1
  103. nautobot/project-static/docs/development/core/style-guide.html +1 -1
  104. nautobot/project-static/docs/development/core/templates.html +1 -1
  105. nautobot/project-static/docs/development/core/testing.html +1 -1
  106. nautobot/project-static/docs/development/core/user-preferences.html +1 -1
  107. nautobot/project-static/docs/development/index.html +1 -1
  108. nautobot/project-static/docs/development/jobs/index.html +142 -118
  109. nautobot/project-static/docs/development/jobs/migration/from-v1.html +1 -1
  110. nautobot/project-static/docs/index.html +1 -1
  111. nautobot/project-static/docs/overview/application_stack.html +1 -1
  112. nautobot/project-static/docs/overview/design_philosophy.html +1 -1
  113. nautobot/project-static/docs/release-notes/index.html +1 -1
  114. nautobot/project-static/docs/release-notes/version-1.0.html +1 -1
  115. nautobot/project-static/docs/release-notes/version-1.1.html +1 -1
  116. nautobot/project-static/docs/release-notes/version-1.2.html +1 -1
  117. nautobot/project-static/docs/release-notes/version-1.3.html +1 -1
  118. nautobot/project-static/docs/release-notes/version-1.4.html +1 -1
  119. nautobot/project-static/docs/release-notes/version-1.5.html +1 -1
  120. nautobot/project-static/docs/release-notes/version-1.6.html +1 -1
  121. nautobot/project-static/docs/release-notes/version-2.0.html +1 -1
  122. nautobot/project-static/docs/release-notes/version-2.1.html +1 -1
  123. nautobot/project-static/docs/release-notes/version-2.2.html +1 -1
  124. nautobot/project-static/docs/release-notes/version-2.3.html +332 -181
  125. nautobot/project-static/docs/requirements.txt +1 -1
  126. nautobot/project-static/docs/search/search_index.json +1 -1
  127. nautobot/project-static/docs/sitemap.xml +270 -270
  128. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  129. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +1 -1
  130. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +1 -1
  131. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +1 -1
  132. nautobot/project-static/docs/user-guide/administration/configuration/index.html +1 -1
  133. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +1 -1
  134. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +1 -1
  135. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +1 -1
  136. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +1 -1
  137. nautobot/project-static/docs/user-guide/administration/guides/docker.html +1 -1
  138. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +1 -1
  139. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +1 -1
  140. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +1 -1
  141. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +1 -1
  142. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +1 -1
  143. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +1 -1
  144. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +1 -1
  145. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +1 -1
  146. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +1 -1
  147. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +1 -1
  148. nautobot/project-static/docs/user-guide/administration/installation/index.html +1 -1
  149. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +1 -1
  150. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +1 -1
  151. nautobot/project-static/docs/user-guide/administration/installation/services.html +1 -1
  152. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +1 -1
  153. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +1 -1
  154. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +1 -1
  155. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +1 -1
  156. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +1 -1
  157. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +1 -1
  158. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +1 -1
  159. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +1 -1
  160. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +1 -1
  161. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +1 -1
  162. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +1 -1
  163. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +1 -1
  164. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +1 -1
  165. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +1 -1
  166. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +1 -1
  167. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +1 -1
  168. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +1 -1
  169. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +1 -1
  170. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +1 -1
  171. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +1 -1
  172. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +1 -1
  173. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +1 -1
  174. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +1 -1
  175. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +1 -1
  176. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +1 -1
  177. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +1 -1
  178. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +1 -1
  179. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +1 -1
  180. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +1 -1
  181. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +1 -1
  182. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +1 -1
  183. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +1 -1
  184. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +1 -1
  185. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +1 -1
  186. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +1 -1
  187. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +1 -1
  188. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +1 -1
  189. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +1 -1
  190. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +1 -1
  191. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +1 -1
  192. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +1 -1
  193. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +1 -1
  194. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +1 -1
  195. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +1 -1
  196. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +1 -1
  197. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +1 -1
  198. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +1 -1
  199. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +1 -1
  200. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +1 -1
  201. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +1 -1
  202. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +1 -1
  203. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +1 -1
  204. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +1 -1
  205. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +1 -1
  206. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +1 -1
  207. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +1 -1
  208. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +1 -1
  209. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +1 -1
  210. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +1 -1
  211. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +1 -1
  212. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +1 -1
  213. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +1 -1
  214. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +1 -1
  215. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +1 -1
  216. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +1 -1
  217. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +1 -1
  218. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +1 -1
  219. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +1 -1
  220. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +1 -1
  221. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +1 -1
  222. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +1 -1
  223. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +1 -1
  224. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +1 -1
  225. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +1 -1
  226. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +1 -1
  227. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +1 -1
  228. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +1 -1
  229. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +1 -1
  230. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +1 -1
  231. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +1 -1
  232. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +1 -1
  233. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +1 -1
  234. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +1 -1
  235. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +1 -1
  236. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +1 -1
  237. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +1 -1
  238. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +1 -1
  239. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +1 -1
  240. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +1 -1
  241. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +1 -1
  242. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +1 -1
  243. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +1 -1
  244. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +1 -1
  245. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +1 -1
  246. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +1 -1
  247. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +1 -1
  248. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +1 -1
  249. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +1 -1
  250. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +1 -1
  251. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +1 -1
  252. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +1 -1
  253. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +1 -1
  254. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +1 -1
  255. nautobot/project-static/docs/user-guide/index.html +1 -1
  256. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +1 -1
  257. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +1 -1
  258. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +1 -1
  259. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +1 -1
  260. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1 -1
  261. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +1 -1
  262. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +1 -1
  263. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +1 -1
  264. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +1 -1
  265. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +1 -1
  266. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +1 -1
  267. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +1 -1
  268. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +1 -1
  269. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +1 -1
  270. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +1 -1
  271. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +1 -1
  272. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +1 -1
  273. nautobot/project-static/docs/user-guide/platform-functionality/note.html +1 -1
  274. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +1 -1
  275. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +1 -1
  276. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +1 -1
  277. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +1 -1
  278. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +1 -1
  279. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +1 -1
  280. nautobot/project-static/docs/user-guide/platform-functionality/role.html +1 -1
  281. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +1 -1
  282. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +1 -1
  283. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +1 -1
  284. nautobot/project-static/docs/user-guide/platform-functionality/status.html +1 -1
  285. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +1 -1
  286. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +1 -1
  287. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +1 -1
  288. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +1 -1
  289. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +1 -1
  290. {nautobot-2.3.13.dist-info → nautobot-2.3.14.dist-info}/METADATA +1 -1
  291. {nautobot-2.3.13.dist-info → nautobot-2.3.14.dist-info}/RECORD +295 -295
  292. {nautobot-2.3.13.dist-info → nautobot-2.3.14.dist-info}/LICENSE.txt +0 -0
  293. {nautobot-2.3.13.dist-info → nautobot-2.3.14.dist-info}/NOTICE +0 -0
  294. {nautobot-2.3.13.dist-info → nautobot-2.3.14.dist-info}/WHEEL +0 -0
  295. {nautobot-2.3.13.dist-info → nautobot-2.3.14.dist-info}/entry_points.txt +0 -0
@@ -110,6 +110,7 @@ class CircuitTable(StatusTableMixin, BaseTable):
110
110
  pk = ToggleColumn()
111
111
  cid = tables.LinkColumn(verbose_name="ID")
112
112
  provider = tables.Column(linkify=True)
113
+ circuit_type = tables.Column(linkify=True)
113
114
  tenant = TenantColumn()
114
115
  tags = TagColumn(url_name="circuits:circuit_list")
115
116
 
@@ -146,7 +147,7 @@ class CircuitTable(StatusTableMixin, BaseTable):
146
147
  "pk",
147
148
  "cid",
148
149
  "provider",
149
- "type",
150
+ "circuit_type",
150
151
  "status",
151
152
  "tenant",
152
153
  "circuit_termination_a",
nautobot/core/filters.py CHANGED
@@ -618,6 +618,7 @@ class BaseFilterSet(django_filters.FilterSet):
618
618
  @staticmethod
619
619
  def _get_filter_lookup_dict(existing_filter):
620
620
  # Choose the lookup expression map based on the filter type
621
+
621
622
  if isinstance(
622
623
  existing_filter,
623
624
  (
@@ -637,6 +638,7 @@ class BaseFilterSet(django_filters.FilterSet):
637
638
  (
638
639
  django_filters.ModelChoiceFilter,
639
640
  django_filters.ModelMultipleChoiceFilter,
641
+ MultiValueUUIDFilter,
640
642
  TagFilter,
641
643
  TreeNodeMultipleChoiceFilter,
642
644
  ),
@@ -1,6 +1,7 @@
1
1
  from datetime import timedelta
2
2
 
3
3
  from django.core.exceptions import PermissionDenied
4
+ from django.db.models import CASCADE
4
5
  from django.db.models.signals import pre_delete
5
6
  from django.utils import timezone
6
7
 
@@ -48,6 +49,27 @@ class LogsCleanup(Job):
48
49
  description = "Delete ObjectChange and/or JobResult/JobLogEntry records older than a specified cutoff."
49
50
  has_sensitive_variables = False
50
51
 
52
+ def recursive_delete_with_cascade(self, queryset, deletion_summary):
53
+ """
54
+ Recursively deletes all related objects with CASCADE for a given queryset.
55
+
56
+ Args:
57
+ queryset (QuerySet): The queryset of objects to delete.
58
+ deletion_summary (dict): A dictionary to store the count of deleted objects for each model.
59
+ """
60
+ related_objects = queryset.model._meta.related_objects
61
+ queryset = queryset.only("id")
62
+
63
+ for related_object in related_objects:
64
+ if related_object.on_delete is CASCADE:
65
+ related_model = related_object.related_model
66
+ related_field_name = related_object.field.name
67
+ cascade_queryset = related_model.objects.filter(**{f"{related_field_name}__id__in": queryset})
68
+ self.recursive_delete_with_cascade(cascade_queryset, deletion_summary)
69
+ _, deleted_dict = queryset.delete()
70
+ deletion_summary.update(deleted_dict)
71
+ return deletion_summary
72
+
51
73
  def run(self, *, cleanup_types, max_age=None):
52
74
  if max_age in (None, ""):
53
75
  max_age = get_settings_or_config("CHANGELOG_RETENTION")
@@ -77,22 +99,36 @@ class LogsCleanup(Job):
77
99
 
78
100
  if CleanupTypes.JOB_RESULT in cleanup_types:
79
101
  self.logger.info("Deleting JobResult records prior to %s", cutoff)
80
- _, deleted_dict = JobResult.objects.restrict(self.user, "delete").filter(date_done__lt=cutoff).delete()
102
+ queryset = JobResult.objects.restrict(self.user, "delete").filter(date_done__lt=cutoff)
103
+ deletion_summary = {}
104
+ self.recursive_delete_with_cascade(queryset, deletion_summary)
81
105
  result.setdefault("extras.JobResult", 0)
82
106
  result.setdefault("extras.JobLogEntry", 0)
83
- result.update(**deleted_dict)
84
- self.logger.info(
85
- "Deleted %d JobResult records and their associated %d JobLogEntry records",
86
- result["extras.JobResult"],
87
- result["extras.JobLogEntry"],
88
- )
107
+ result.update(deletion_summary)
108
+
109
+ for modelname, count in deletion_summary.items():
110
+ self.logger.info(
111
+ "As part of deleting %d JobResult records, also deleted %d related %s records",
112
+ result["extras.JobResult"],
113
+ count,
114
+ modelname,
115
+ )
89
116
 
90
117
  if CleanupTypes.OBJECT_CHANGE in cleanup_types:
91
118
  self.logger.info("Deleting ObjectChange records prior to %s", cutoff)
92
- deleted_count, _ = ObjectChange.objects.restrict(self.user, "delete").filter(time__lt=cutoff).delete()
93
- self.logger.info("Deleted %d ObjectChange records", deleted_count)
94
- result["extras.ObjectChange"] = deleted_count
95
-
119
+ queryset = ObjectChange.objects.restrict(self.user, "delete").filter(time__lt=cutoff)
120
+ deletion_summary = {}
121
+ self.recursive_delete_with_cascade(queryset, deletion_summary)
122
+ result.setdefault("extras.ObjectChange", 0)
123
+ result.update(deletion_summary)
124
+
125
+ for modelname, count in deletion_summary.items():
126
+ self.logger.info(
127
+ "As part of deleting %d ObjectChange records, also deleted %d related %s records",
128
+ result["extras.ObjectChange"],
129
+ count,
130
+ modelname,
131
+ )
96
132
  return result
97
133
  finally:
98
134
  # Be sure to clean up after ourselves!
@@ -65,3 +65,10 @@
65
65
  </div>
66
66
  {% endif %}
67
67
  {% endblock %}
68
+
69
+ {% block javascript %}
70
+ {{ block.super }}
71
+ <script>
72
+ var clipboard = new ClipboardJS('.btn');
73
+ </script>
74
+ {% endblock %}
@@ -88,11 +88,26 @@ class FilterTestCases:
88
88
  return self.filterset.declared_filters["q"].filter_predicates
89
89
 
90
90
  def test_id(self):
91
- """Verify that the filterset supports filtering by id."""
92
- params = {"id": self.queryset.values_list("pk", flat=True)[:2]}
93
- filterset = self.filterset(params, self.queryset)
94
- self.assertTrue(filterset.is_valid())
95
- self.assertEqual(filterset.qs.count(), 2)
91
+ """Verify that the filterset supports filtering by id with only lookup `__n`."""
92
+ with self.subTest("Assert `id`"):
93
+ params = {"id": list(self.queryset.values_list("pk", flat=True)[:2])}
94
+ expected_queryset = self.queryset.filter(id__in=params["id"])
95
+ filterset = self.filterset(params, self.queryset)
96
+ self.assertTrue(filterset.is_valid())
97
+ self.assertQuerysetEqualAndNotEmpty(filterset.qs.order_by("id"), expected_queryset.order_by("id"))
98
+
99
+ with self.subTest("Assert negate lookup"):
100
+ params = {"id__n": list(self.queryset.values_list("pk", flat=True)[:2])}
101
+ expected_queryset = self.queryset.exclude(id__in=params["id__n"])
102
+ filterset = self.filterset(params, self.queryset)
103
+ self.assertTrue(filterset.is_valid())
104
+ self.assertQuerysetEqualAndNotEmpty(filterset.qs.order_by("id"), expected_queryset.order_by("id"))
105
+
106
+ with self.subTest("Assert invalid lookup"):
107
+ params = {"id__in": list(self.queryset.values_list("pk", flat=True)[:2])}
108
+ filterset = self.filterset(params, self.queryset)
109
+ self.assertFalse(filterset.is_valid())
110
+ self.assertIn("Unknown filter field", filterset.errors.as_text())
96
111
 
97
112
  def test_invalid_filter(self):
98
113
  """Verify that the filterset reports as invalid when initialized with an unsupported filter parameter."""
nautobot/dcim/forms.py CHANGED
@@ -792,7 +792,7 @@ class DeviceFamilyFilterForm(NautobotFilterForm):
792
792
  tags = TagFilterField(model)
793
793
 
794
794
 
795
- class DeviceFamilyBulkEditForm(NautobotBulkEditForm, TagsBulkEditFormMixin):
795
+ class DeviceFamilyBulkEditForm(TagsBulkEditFormMixin, NautobotBulkEditForm):
796
796
  pk = forms.ModelMultipleChoiceField(queryset=DeviceFamily.objects.all(), widget=forms.MultipleHiddenInput())
797
797
  description = forms.CharField(required=False)
798
798
 
@@ -3183,9 +3183,6 @@ class InterfaceBulkEditForm(
3183
3183
  untagged_vlan = DynamicModelChoiceField(
3184
3184
  queryset=VLAN.objects.all(),
3185
3185
  required=False,
3186
- query_params={
3187
- "locations": "null",
3188
- },
3189
3186
  )
3190
3187
  tagged_vlans = DynamicModelMultipleChoiceField(
3191
3188
  queryset=VLAN.objects.all(),
@@ -3235,8 +3232,12 @@ class InterfaceBulkEditForm(
3235
3232
  # Limit VLAN choices by Location
3236
3233
  if locations.count() == 1:
3237
3234
  location = locations.first()
3238
- self.fields["untagged_vlan"].widget.add_query_param("locations", location.pk)
3235
+ # In the case of a single location, use the available_on_device query param to limit untagged VLAN choices
3236
+ # to those available on the devices in that location and in the ancestors of the location.
3237
+ self.fields["untagged_vlan"].widget.add_query_param("available_on_device", device.pk)
3239
3238
  self.fields["tagged_vlans"].widget.add_query_param("locations", location.pk)
3239
+ else:
3240
+ self.fields["tagged_vlans"].widget.add_query_param("locations", "null")
3240
3241
 
3241
3242
  # Restrict parent/bridge/LAG interface assignment by device (or VC master)
3242
3243
  if device_count == 1:
@@ -47,6 +47,7 @@ __all__ = (
47
47
  "Controller",
48
48
  "ControllerManagedDeviceGroup",
49
49
  "Device",
50
+ "DeviceFamily",
50
51
  "DeviceRedundancyGroup",
51
52
  "DeviceType",
52
53
  "Manufacturer",
@@ -1,8 +1,16 @@
1
1
  from django.test import TestCase
2
2
 
3
3
  from nautobot.core.testing.forms import FormTestCases
4
+ from nautobot.core.testing.mixins import NautobotTestCaseMixin
4
5
  from nautobot.dcim.choices import DeviceFaceChoices, InterfaceModeChoices, InterfaceTypeChoices, RackWidthChoices
5
- from nautobot.dcim.forms import DeviceFilterForm, DeviceForm, InterfaceCreateForm, InterfaceForm, RackForm
6
+ from nautobot.dcim.forms import (
7
+ DeviceFilterForm,
8
+ DeviceForm,
9
+ InterfaceBulkEditForm,
10
+ InterfaceCreateForm,
11
+ InterfaceForm,
12
+ RackForm,
13
+ )
6
14
  from nautobot.dcim.models import (
7
15
  Device,
8
16
  DeviceType,
@@ -320,7 +328,7 @@ class RackTestCase(TestCase):
320
328
  self.assertTrue(form.is_valid())
321
329
 
322
330
 
323
- class InterfaceTestCase(TestCase):
331
+ class InterfaceTestCase(NautobotTestCaseMixin, TestCase):
324
332
  @classmethod
325
333
  def setUpTestData(cls):
326
334
  cls.device = Device.objects.first()
@@ -380,3 +388,44 @@ class InterfaceTestCase(TestCase):
380
388
  self.vlan.locations.clear()
381
389
  form = InterfaceForm(data=self.data, instance=self.interface)
382
390
  self.assertTrue(form.is_valid())
391
+
392
+ def test_untagged_vlans_dropdown_options_align_in_interface_edit_form_and_bulk_edit_form(self):
393
+ """
394
+ Assert that untagged_vlans field dropdown are populated correctly in InterfaceForm and InterfaceBulkEditForm,
395
+ and that the queryset is the same for both forms.
396
+ """
397
+ status = Status.objects.get_for_model(Interface).first()
398
+ location = Location.objects.filter(location_type=LocationType.objects.get(name="Campus")).first()
399
+ devices = Device.objects.all()[:3]
400
+ for device in devices:
401
+ device.location = location
402
+ device.save()
403
+ interfaces = (
404
+ Interface.objects.create(
405
+ device=devices[0],
406
+ name="Test Interface 1",
407
+ type=InterfaceTypeChoices.TYPE_2GFC_SFP,
408
+ status=status,
409
+ ),
410
+ Interface.objects.create(
411
+ device=devices[1],
412
+ name="Test Interface 2",
413
+ type=InterfaceTypeChoices.TYPE_LAG,
414
+ status=status,
415
+ ),
416
+ Interface.objects.create(
417
+ device=devices[2],
418
+ name="Test Interface 3",
419
+ type=InterfaceTypeChoices.TYPE_100ME_FIXED,
420
+ status=status,
421
+ ),
422
+ )
423
+ edit_form = InterfaceForm(data=self.data, instance=interfaces[0])
424
+ bulk_edit_form = InterfaceBulkEditForm(
425
+ model=Interface,
426
+ data={"pks": [interface.pk for interface in interfaces]},
427
+ )
428
+ self.assertQuerysetEqualAndNotEmpty(
429
+ edit_form.fields["untagged_vlan"].queryset,
430
+ bulk_edit_form.fields["untagged_vlan"].queryset,
431
+ )
@@ -842,8 +842,16 @@ class TagsBulkEditFormMixin(forms.Form):
842
842
  super().__init__(*args, **kwargs)
843
843
 
844
844
  # Add add/remove tags fields
845
- self.fields["add_tags"] = DynamicModelMultipleChoiceField(queryset=Tag.objects.all(), required=False)
846
- self.fields["remove_tags"] = DynamicModelMultipleChoiceField(queryset=Tag.objects.all(), required=False)
845
+ self.fields["add_tags"] = DynamicModelMultipleChoiceField(
846
+ queryset=Tag.objects.all(),
847
+ query_params={"content_types": self.model._meta.label_lower},
848
+ required=False,
849
+ )
850
+ self.fields["remove_tags"] = DynamicModelMultipleChoiceField(
851
+ queryset=Tag.objects.all(),
852
+ query_params={"content_types": self.model._meta.label_lower},
853
+ required=False,
854
+ )
847
855
 
848
856
 
849
857
  # 2.2 TODO: Names below are only for backward compatibility with Nautobot 1.3 and earlier. Remove in 2.2
nautobot/extras/jobs.py CHANGED
@@ -98,13 +98,15 @@ class BaseJob:
98
98
 
99
99
  - name (str)
100
100
  - description (str)
101
- - hidden (bool)
102
- - field_order (list)
103
101
  - approval_required (bool)
104
- - soft_time_limit (int)
105
- - time_limit (int)
102
+ - dryrun_default (bool)
103
+ - field_order (list)
106
104
  - has_sensitive_variables (bool)
105
+ - hidden (bool)
106
+ - soft_time_limit (int)
107
107
  - task_queues (list)
108
+ - template_name (str)
109
+ - time_limit (int)
108
110
  """
109
111
 
110
112
  def __init__(self):
nautobot/ipam/models.py CHANGED
@@ -531,8 +531,32 @@ class Prefix(PrimaryModel):
531
531
  prefix = kwargs.pop("prefix", None)
532
532
  self._location = kwargs.pop("location", None)
533
533
  super().__init__(*args, **kwargs)
534
+
535
+ # Initialize cached fields
536
+ self._parent = None
537
+ self._network = None
538
+ self._prefix_length = None
539
+ self._namespace_id = None
540
+
534
541
  self._deconstruct_prefix(prefix)
535
542
 
543
+ @staticmethod
544
+ def _extract_field_value(field_name, field_names, values):
545
+ for current_field, field_value in zip(field_names, values):
546
+ if field_name == current_field:
547
+ return field_value
548
+ return None
549
+
550
+ @classmethod
551
+ def from_db(cls, db, field_names, values):
552
+ instance = super().from_db(db, field_names, values)
553
+ # These cached values are used to detect changes during save,
554
+ # avoiding unnecessary re-parenting of subnets and IPs if these fields have not been updated.
555
+ instance._network = cls._extract_field_value("network", field_names, values)
556
+ instance._prefix_length = cls._extract_field_value("prefix_length", field_names, values)
557
+ instance._namespace_id = cls._extract_field_value("namespace_id", field_names, values)
558
+ return instance
559
+
536
560
  def __str__(self):
537
561
  return str(self.prefix)
538
562
 
@@ -612,6 +636,23 @@ class Prefix(PrimaryModel):
612
636
  protected_objects.update(parent=self.parent)
613
637
  return super().delete(*args, **kwargs)
614
638
 
639
+ def get_parent(self):
640
+ # Determine if a parent exists and set it to the closest ancestor by `prefix_length`.
641
+ if self._parent is not None:
642
+ return self._parent
643
+
644
+ if supernets := self.supernets():
645
+ parent = max(supernets, key=operator.attrgetter("prefix_length"))
646
+ self._parent = parent
647
+ return parent
648
+ return None
649
+
650
+ def clean(self):
651
+ if self.prefix:
652
+ # self.parent depends on self.prefix having a value
653
+ self.parent = self.get_parent()
654
+ super().clean()
655
+
615
656
  def save(self, *args, **kwargs):
616
657
  if isinstance(self.prefix, netaddr.IPNetwork):
617
658
  # Clear host bits from prefix
@@ -619,11 +660,7 @@ class Prefix(PrimaryModel):
619
660
  # which will (re)set the broadcast and ip_version values of this instance to their correct values.
620
661
  self.prefix = self.prefix.cidr
621
662
 
622
- # Determine if a parent exists and set it to the closest ancestor by `prefix_length`.
623
- supernets = self.supernets()
624
- if supernets:
625
- parent = max(supernets, key=operator.attrgetter("prefix_length"))
626
- self.parent = parent
663
+ self.parent = self.get_parent()
627
664
 
628
665
  # Validate that creation of this prefix does not create an invalid parent/child relationship
629
666
  # 3.0 TODO: uncomment this to enforce this constraint
@@ -655,15 +692,26 @@ class Prefix(PrimaryModel):
655
692
  # )
656
693
  # raise ValidationError({"__all__": err_msg})
657
694
 
695
+ # cache the value of present_in_database; because after `super().save()`
696
+ # `self.present_in_database` would always return True`
697
+ present_in_database = self.present_in_database
698
+
658
699
  with transaction.atomic():
659
700
  super().save(*args, **kwargs)
660
701
  if self._location is not None:
661
702
  self.location = self._location
662
703
 
663
- # Determine the subnets and reparent them to this prefix.
664
- self.reparent_subnets()
665
- # Determine the child IPs and reparent them to this prefix.
666
- self.reparent_ips()
704
+ # Only reparent subnets and ips if any of these fields has been updated.
705
+ if (
706
+ not present_in_database
707
+ or self._network != self.network
708
+ or self._namespace_id != self.namespace_id
709
+ or self._prefix_length != self.prefix_length
710
+ ):
711
+ # Determine the subnets and reparent them to this prefix.
712
+ self.reparent_subnets()
713
+ # Determine the child IPs and reparent them to this prefix.
714
+ self.reparent_ips()
667
715
 
668
716
  @property
669
717
  def cidr_str(self):
@@ -739,6 +787,7 @@ class Prefix(PrimaryModel):
739
787
  Returns:
740
788
  QuerySet
741
789
  """
790
+
742
791
  query = Prefix.objects.all()
743
792
 
744
793
  if for_update:
@@ -750,7 +799,7 @@ class Prefix(PrimaryModel):
750
799
  if not include_self:
751
800
  query = query.exclude(id=self.id)
752
801
 
753
- return query.filter(
802
+ supernets = query.filter(
754
803
  ip_version=self.ip_version,
755
804
  prefix_length__lte=self.prefix_length,
756
805
  network__lte=self.network,
@@ -758,6 +807,8 @@ class Prefix(PrimaryModel):
758
807
  namespace=self.namespace,
759
808
  )
760
809
 
810
+ return supernets
811
+
761
812
  def subnets(self, direct=False, include_self=False, for_update=False):
762
813
  """
763
814
  Return subnets of this Prefix.
@@ -867,7 +918,7 @@ class Prefix(PrimaryModel):
867
918
  Return all available IPs within this prefix as an IPSet.
868
919
  """
869
920
  prefix = netaddr.IPSet(self.prefix)
870
- child_ips = netaddr.IPSet([ip.address.ip for ip in self.ip_addresses.all()])
921
+ child_ips = netaddr.IPSet([ip.address.ip for ip in self.get_all_ips()])
871
922
  available_ips = prefix - child_ips
872
923
 
873
924
  # IPv6, pool, or IPv4 /31-32 sets are fully usable
nautobot/ipam/tables.py CHANGED
@@ -404,7 +404,7 @@ class PrefixDetailTable(PrefixTable):
404
404
  "type",
405
405
  "status",
406
406
  "children",
407
- # "vrf",
407
+ "vrf_count",
408
408
  "utilization",
409
409
  "tenant",
410
410
  "location_count",
@@ -420,7 +420,7 @@ class PrefixDetailTable(PrefixTable):
420
420
  "type",
421
421
  "status",
422
422
  "children",
423
- # "vrf",
423
+ "vrf_count",
424
424
  "utilization",
425
425
  "tenant",
426
426
  "location_count",
@@ -594,9 +594,76 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
594
594
  response = self.client.get(url, **self.header)
595
595
  self.assertEqual(len(response.data), 6) # 8 - 2 because prefix.type = network
596
596
 
597
+ def test_list_available_ips_calculate_child_ips(self):
598
+ """
599
+ Test retrieval of all available IP addresses when child IP's exists.
600
+ """
601
+ ip_status = Status.objects.get_for_model(IPAddress).first()
602
+ prefix = Prefix.objects.create(
603
+ prefix="192.0.3.0/29",
604
+ type=choices.PrefixTypeChoices.TYPE_POOL,
605
+ namespace=self.namespace,
606
+ status=self.status,
607
+ )
608
+ Prefix.objects.create(
609
+ prefix="192.0.3.0/30",
610
+ type=choices.PrefixTypeChoices.TYPE_POOL,
611
+ namespace=self.namespace,
612
+ status=self.status,
613
+ )
614
+ IPAddress.objects.create(
615
+ address="192.0.3.1/30",
616
+ status=ip_status,
617
+ namespace=self.namespace,
618
+ )
619
+
620
+ url = reverse("ipam-api:prefix-available-ips", kwargs={"pk": prefix.pk})
621
+ self.add_permissions("ipam.view_prefix", "ipam.view_ipaddress")
622
+
623
+ # Retrieve all available IPs
624
+ response = self.client.get(url, **self.header)
625
+ self.assertEqual(len(response.data), 7) # 7 because prefix.type = pool got 8 IP's minus one children IP
626
+
627
+ def test_create_single_available_ip_calculate_child_ips(self):
628
+ """
629
+ Test creating a single IP when child IP's exists.
630
+ """
631
+ ip_status = Status.objects.get_for_model(IPAddress).first()
632
+ prefix = Prefix.objects.create(
633
+ prefix="192.0.4.0/31",
634
+ namespace=self.namespace,
635
+ type=choices.PrefixTypeChoices.TYPE_NETWORK,
636
+ status=self.status,
637
+ )
638
+ Prefix.objects.create(
639
+ prefix="192.0.4.0/32",
640
+ type=choices.PrefixTypeChoices.TYPE_POOL,
641
+ namespace=self.namespace,
642
+ status=self.status,
643
+ )
644
+ IPAddress.objects.create(
645
+ address="192.0.4.0/32",
646
+ status=ip_status,
647
+ namespace=self.namespace,
648
+ )
649
+ url = reverse("ipam-api:prefix-available-ips", kwargs={"pk": prefix.pk})
650
+ self.add_permissions("ipam.view_prefix", "ipam.add_ipaddress", "extras.view_status")
651
+
652
+ data = {
653
+ "status": self.status.pk,
654
+ }
655
+
656
+ response = self.client.post(url, data, format="json", **self.header)
657
+ self.assertHttpStatus(response, status.HTTP_201_CREATED)
658
+ self.assertEqual(str(response.data["parent"]["url"]), self.absolute_api_url(prefix))
659
+
660
+ response = self.client.post(url, data, format="json", **self.header)
661
+ self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
662
+ self.assertIn("detail", response.data)
663
+
597
664
  def test_create_single_available_ip(self):
598
665
  """
599
- Test retrieval of the first available IP address within a parent prefix.
666
+ Test creating single IP will return 204 No content when pool is fully filled.
600
667
  """
601
668
  prefix = Prefix.objects.create(
602
669
  prefix="192.0.2.0/29",
@@ -1,4 +1,5 @@
1
1
  from unittest import skipIf
2
+ from unittest.mock import patch
2
3
 
3
4
  from django.contrib.contenttypes.models import ContentType
4
5
  from django.core.exceptions import ValidationError
@@ -413,6 +414,39 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
413
414
  self.child1 = Prefix.objects.create(prefix="101.102.0.0/26", status=self.status, namespace=self.namespace)
414
415
  self.child2 = Prefix.objects.create(prefix="101.102.0.64/26", status=self.status, namespace=self.namespace)
415
416
 
417
+ def test_parent_exists_after_model_clean(self):
418
+ prefix = Prefix(
419
+ prefix="101.102.0.2/26", status=self.status, namespace=self.namespace, type=PrefixTypeChoices.TYPE_CONTAINER
420
+ )
421
+ prefix.clean()
422
+ self.assertEqual(prefix.parent, self.child1)
423
+
424
+ def test_reparent_subnets_and_reparent_ips_call(self):
425
+ """Assert reparent_subnets and reparent_ips are only called if there is an update to either of network, namespace or prefix_length"""
426
+ prefix_ip = "101.102.0.0/28"
427
+ with self.subTest("Assert reparent_subnets"):
428
+ with patch.object(Prefix, "reparent_subnets", return_value=None) as mock_reparent_subnets:
429
+ Prefix.objects.create(prefix=prefix_ip, status=self.status, namespace=self.namespace)
430
+ mock_reparent_subnets.assert_called_once()
431
+
432
+ with patch.object(Prefix, "reparent_subnets", return_value=None) as mock_reparent_subnets:
433
+ prefix = Prefix.objects.get(prefix=prefix_ip)
434
+ prefix.description = "Sample Description"
435
+ prefix.save()
436
+ mock_reparent_subnets.assert_not_called()
437
+ prefix.delete()
438
+
439
+ with self.subTest("Assert reparent_ips"):
440
+ with patch.object(Prefix, "reparent_ips", return_value=None) as reparent_ips:
441
+ Prefix.objects.create(prefix=prefix_ip, status=self.status, namespace=self.namespace)
442
+ reparent_ips.assert_called_once()
443
+
444
+ with patch.object(Prefix, "reparent_ips", return_value=None) as reparent_ips:
445
+ prefix = Prefix.objects.get(prefix=prefix_ip)
446
+ prefix.description = "Sample Description"
447
+ prefix.save()
448
+ reparent_ips.assert_not_called()
449
+
416
450
  def test_location_queries(self):
417
451
  locations = Location.objects.all()[:4]
418
452
  for location in locations:
@@ -766,6 +800,13 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
766
800
  IPAddress.objects.create(address="10.0.0.4/24", status=self.status, namespace=self.namespace)
767
801
  self.assertEqual(parent_prefix.get_first_available_ip(), "10.0.0.5/24")
768
802
 
803
+ def test_get_first_available_ip_calculate_child_ips(self):
804
+ parent_prefix = Prefix.objects.create(prefix="10.0.3.0/29", status=self.status, namespace=self.namespace)
805
+ Prefix.objects.create(prefix="10.0.3.0/30", status=self.status, namespace=self.namespace)
806
+ IPAddress(address="10.0.3.1/30", status=self.status, namespace=self.namespace).save()
807
+
808
+ self.assertEqual(parent_prefix.get_first_available_ip(), "10.0.3.2/29")
809
+
769
810
  def test_get_utilization(self):
770
811
  # Container Prefix
771
812
  prefix = Prefix.objects.create(
@@ -12,7 +12,7 @@
12
12
 
13
13
 
14
14
  <link rel="icon" href="/projects/core/en/stable/assets/favicon.ico">
15
- <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.47">
15
+ <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.48">
16
16
 
17
17
 
18
18
 
@@ -18,7 +18,7 @@
18
18
 
19
19
 
20
20
  <link rel="icon" href="../assets/favicon.ico">
21
- <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.47">
21
+ <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.48">
22
22
 
23
23
 
24
24
 
@@ -16,7 +16,7 @@
16
16
 
17
17
 
18
18
  <link rel="icon" href="../assets/favicon.ico">
19
- <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.47">
19
+ <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.48">
20
20
 
21
21
 
22
22
 
@@ -18,7 +18,7 @@
18
18
 
19
19
 
20
20
  <link rel="icon" href="../../../assets/favicon.ico">
21
- <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.47">
21
+ <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.48">
22
22
 
23
23
 
24
24
 
@@ -18,7 +18,7 @@
18
18
 
19
19
 
20
20
  <link rel="icon" href="../../../assets/favicon.ico">
21
- <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.47">
21
+ <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.48">
22
22
 
23
23
 
24
24