nautobot 2.3.12__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 (324) hide show
  1. nautobot/circuits/tables.py +2 -1
  2. nautobot/core/api/serializers.py +1 -0
  3. nautobot/core/celery/log.py +4 -4
  4. nautobot/core/filters.py +2 -0
  5. nautobot/core/jobs/cleanup.py +47 -11
  6. nautobot/core/models/tree_queries.py +5 -2
  7. nautobot/core/settings.py +1 -1
  8. nautobot/core/tables.py +60 -10
  9. nautobot/core/templates/search.html +7 -0
  10. nautobot/core/templatetags/helpers.py +7 -1
  11. nautobot/core/testing/api.py +5 -1
  12. nautobot/core/testing/filters.py +20 -5
  13. nautobot/core/tests/test_api.py +20 -0
  14. nautobot/core/tests/test_csv.py +25 -3
  15. nautobot/core/tests/test_utils.py +8 -0
  16. nautobot/core/utils/lookup.py +11 -8
  17. nautobot/dcim/api/views.py +3 -0
  18. nautobot/dcim/filters/__init__.py +26 -1
  19. nautobot/dcim/forms.py +10 -5
  20. nautobot/dcim/models/devices.py +1 -0
  21. nautobot/dcim/tests/test_filters.py +33 -0
  22. nautobot/dcim/tests/test_forms.py +51 -2
  23. nautobot/dcim/tests/test_views.py +6 -0
  24. nautobot/extras/api/serializers.py +1 -0
  25. nautobot/extras/api/views.py +2 -0
  26. nautobot/extras/forms/forms.py +2 -0
  27. nautobot/extras/forms/mixins.py +10 -2
  28. nautobot/extras/group_sync.py +3 -3
  29. nautobot/extras/jobs.py +6 -4
  30. nautobot/extras/plugins/__init__.py +13 -2
  31. nautobot/extras/tests/test_views.py +2 -0
  32. nautobot/ipam/lookups.py +101 -62
  33. nautobot/ipam/models.py +62 -11
  34. nautobot/ipam/tables.py +20 -6
  35. nautobot/ipam/tests/test_api.py +68 -1
  36. nautobot/ipam/tests/test_models.py +41 -0
  37. nautobot/ipam/tests/test_querysets.py +49 -1
  38. nautobot/ipam/utils/__init__.py +24 -0
  39. nautobot/ipam/views.py +61 -68
  40. nautobot/project-static/docs/404.html +1 -1
  41. nautobot/project-static/docs/apps/index.html +1 -1
  42. nautobot/project-static/docs/apps/nautobot-apps.html +1 -1
  43. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +1 -1
  44. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +1 -1
  45. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1 -1
  46. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +1 -1
  47. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +1 -1
  48. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +1 -1
  49. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +1 -1
  50. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +1 -1
  51. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +1 -1
  52. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +1 -1
  53. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1 -1
  54. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1 -1
  55. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +1 -1
  56. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +7 -5
  57. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +1 -1
  58. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +1 -1
  59. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +1 -1
  60. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +197 -5
  61. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +2 -2
  62. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1 -1
  63. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +1 -1
  64. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +16 -2
  65. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1 -1
  66. nautobot/project-static/docs/development/apps/api/configuration-view.html +1 -1
  67. nautobot/project-static/docs/development/apps/api/database-backend-config.html +1 -1
  68. nautobot/project-static/docs/development/apps/api/models/django-admin.html +1 -1
  69. nautobot/project-static/docs/development/apps/api/models/global-search.html +1 -1
  70. nautobot/project-static/docs/development/apps/api/models/graphql.html +1 -1
  71. nautobot/project-static/docs/development/apps/api/models/index.html +1 -1
  72. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +1 -1
  73. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +1 -1
  74. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +1 -1
  75. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +1 -1
  76. nautobot/project-static/docs/development/apps/api/platform-features/index.html +1 -1
  77. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +1 -1
  78. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +1 -1
  79. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +1 -1
  80. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +1 -1
  81. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +23 -4
  82. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +1 -1
  83. nautobot/project-static/docs/development/apps/api/prometheus.html +1 -1
  84. nautobot/project-static/docs/development/apps/api/setup.html +1 -1
  85. nautobot/project-static/docs/development/apps/api/testing.html +1 -1
  86. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +1 -1
  87. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +1 -1
  88. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +1 -1
  89. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +1 -1
  90. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +1 -1
  91. nautobot/project-static/docs/development/apps/api/views/base-template.html +1 -1
  92. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +1 -1
  93. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +1 -1
  94. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +1 -1
  95. nautobot/project-static/docs/development/apps/api/views/index.html +1 -1
  96. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +1 -1
  97. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +1 -1
  98. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +1 -1
  99. nautobot/project-static/docs/development/apps/api/views/notes.html +1 -1
  100. nautobot/project-static/docs/development/apps/api/views/rest-api.html +1 -1
  101. nautobot/project-static/docs/development/apps/api/views/urls.html +1 -1
  102. nautobot/project-static/docs/development/apps/index.html +1 -1
  103. nautobot/project-static/docs/development/apps/migration/code-updates.html +1 -1
  104. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +1 -1
  105. nautobot/project-static/docs/development/apps/migration/from-v1.html +1 -1
  106. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +1 -1
  107. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +1 -1
  108. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +1 -1
  109. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +1 -1
  110. nautobot/project-static/docs/development/apps/porting-from-netbox.html +1 -1
  111. nautobot/project-static/docs/development/core/application-registry.html +1 -1
  112. nautobot/project-static/docs/development/core/best-practices.html +1 -1
  113. nautobot/project-static/docs/development/core/bootstrap-ui.html +1 -1
  114. nautobot/project-static/docs/development/core/caching.html +1 -1
  115. nautobot/project-static/docs/development/core/controllers.html +1 -1
  116. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +1 -1
  117. nautobot/project-static/docs/development/core/generic-views.html +1 -1
  118. nautobot/project-static/docs/development/core/getting-started.html +154 -140
  119. nautobot/project-static/docs/development/core/homepage.html +1 -1
  120. nautobot/project-static/docs/development/core/index.html +1 -1
  121. nautobot/project-static/docs/development/core/model-checklist.html +1 -1
  122. nautobot/project-static/docs/development/core/model-features.html +1 -1
  123. nautobot/project-static/docs/development/core/natural-keys.html +1 -1
  124. nautobot/project-static/docs/development/core/navigation-menu.html +1 -1
  125. nautobot/project-static/docs/development/core/release-checklist.html +1 -1
  126. nautobot/project-static/docs/development/core/role-internals.html +1 -1
  127. nautobot/project-static/docs/development/core/settings.html +1 -1
  128. nautobot/project-static/docs/development/core/style-guide.html +1 -1
  129. nautobot/project-static/docs/development/core/templates.html +1 -1
  130. nautobot/project-static/docs/development/core/testing.html +1 -1
  131. nautobot/project-static/docs/development/core/user-preferences.html +1 -1
  132. nautobot/project-static/docs/development/index.html +1 -1
  133. nautobot/project-static/docs/development/jobs/index.html +142 -118
  134. nautobot/project-static/docs/development/jobs/migration/from-v1.html +1 -1
  135. nautobot/project-static/docs/index.html +1 -1
  136. nautobot/project-static/docs/objects.inv +0 -0
  137. nautobot/project-static/docs/overview/application_stack.html +1 -1
  138. nautobot/project-static/docs/overview/design_philosophy.html +1 -1
  139. nautobot/project-static/docs/release-notes/index.html +1 -1
  140. nautobot/project-static/docs/release-notes/version-1.0.html +1 -1
  141. nautobot/project-static/docs/release-notes/version-1.1.html +1 -1
  142. nautobot/project-static/docs/release-notes/version-1.2.html +1 -1
  143. nautobot/project-static/docs/release-notes/version-1.3.html +1 -1
  144. nautobot/project-static/docs/release-notes/version-1.4.html +1 -1
  145. nautobot/project-static/docs/release-notes/version-1.5.html +1 -1
  146. nautobot/project-static/docs/release-notes/version-1.6.html +614 -179
  147. nautobot/project-static/docs/release-notes/version-2.0.html +1 -1
  148. nautobot/project-static/docs/release-notes/version-2.1.html +1 -1
  149. nautobot/project-static/docs/release-notes/version-2.2.html +1 -1
  150. nautobot/project-static/docs/release-notes/version-2.3.html +553 -200
  151. nautobot/project-static/docs/requirements.txt +1 -1
  152. nautobot/project-static/docs/search/search_index.json +1 -1
  153. nautobot/project-static/docs/sitemap.xml +270 -270
  154. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  155. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +1 -1
  156. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +1 -1
  157. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +3 -3
  158. nautobot/project-static/docs/user-guide/administration/configuration/index.html +1 -1
  159. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +1 -1
  160. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +1 -1
  161. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +1 -1
  162. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +1 -1
  163. nautobot/project-static/docs/user-guide/administration/guides/docker.html +1 -1
  164. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +1 -1
  165. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +1 -1
  166. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +1 -1
  167. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +1 -1
  168. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +1 -1
  169. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +1 -1
  170. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +1 -1
  171. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +1 -1
  172. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +1 -1
  173. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +1 -1
  174. nautobot/project-static/docs/user-guide/administration/installation/index.html +1 -1
  175. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +1 -1
  176. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +1 -1
  177. nautobot/project-static/docs/user-guide/administration/installation/services.html +1 -1
  178. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +1 -1
  179. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +1 -1
  180. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +1 -1
  181. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +1 -1
  182. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +1 -1
  183. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +1 -1
  184. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +1 -1
  185. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +1 -1
  186. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +1 -1
  187. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +1 -1
  188. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +1 -1
  189. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +1 -1
  190. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +1 -1
  191. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +1 -1
  192. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +1 -1
  193. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +1 -1
  194. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +1 -1
  195. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +1 -1
  196. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +1 -1
  197. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +1 -1
  198. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +1 -1
  199. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +1 -1
  200. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +1 -1
  201. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +1 -1
  202. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +1 -1
  203. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +1 -1
  204. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +1 -1
  205. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +1 -1
  206. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +1 -1
  207. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +1 -1
  208. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +1 -1
  209. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +1 -1
  210. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +1 -1
  211. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +1 -1
  212. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +1 -1
  213. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +1 -1
  214. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +1 -1
  215. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +1 -1
  216. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +1 -1
  217. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +1 -1
  218. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +1 -1
  219. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +1 -1
  220. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +1 -1
  221. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +1 -1
  222. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +1 -1
  223. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +1 -1
  224. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +1 -1
  225. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +1 -1
  226. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +1 -1
  227. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +1 -1
  228. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +1 -1
  229. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +1 -1
  230. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +1 -1
  231. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +1 -1
  232. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +1 -1
  233. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +1 -1
  234. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +1 -1
  235. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +1 -1
  236. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +1 -1
  237. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +1 -1
  238. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +1 -1
  239. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +1 -1
  240. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +1 -1
  241. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +1 -1
  242. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +1 -1
  243. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +1 -1
  244. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +1 -1
  245. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +1 -1
  246. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +1 -1
  247. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +1 -1
  248. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +1 -1
  249. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +1 -1
  250. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +1 -1
  251. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +1 -1
  252. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +1 -1
  253. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +1 -1
  254. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +1 -1
  255. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +1 -1
  256. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +1 -1
  257. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +1 -1
  258. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +1 -1
  259. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +1 -1
  260. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +1 -1
  261. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +1 -1
  262. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +1 -1
  263. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +1 -1
  264. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +1 -1
  265. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +1 -1
  266. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +1 -1
  267. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +1 -1
  268. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +1 -1
  269. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +1 -1
  270. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +1 -1
  271. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +1 -1
  272. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +1 -1
  273. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +1 -1
  274. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +1 -1
  275. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +1 -1
  276. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +1 -1
  277. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +1 -1
  278. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +1 -1
  279. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +1 -1
  280. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +1 -1
  281. nautobot/project-static/docs/user-guide/index.html +1 -1
  282. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +1 -1
  283. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +1 -1
  284. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +1 -1
  285. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +1 -1
  286. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1 -1
  287. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +1 -1
  288. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +1 -1
  289. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +1 -1
  290. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +1 -1
  291. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +1 -1
  292. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +1 -1
  293. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +1 -1
  294. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +1 -1
  295. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +1 -1
  296. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +1 -1
  297. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +1 -1
  298. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +1 -1
  299. nautobot/project-static/docs/user-guide/platform-functionality/note.html +1 -1
  300. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +1 -1
  301. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +1 -1
  302. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +1 -1
  303. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +1 -1
  304. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +1 -1
  305. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +1 -1
  306. nautobot/project-static/docs/user-guide/platform-functionality/role.html +1 -1
  307. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +1 -1
  308. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +1 -1
  309. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +1 -1
  310. nautobot/project-static/docs/user-guide/platform-functionality/status.html +1 -1
  311. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +1 -1
  312. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +1 -1
  313. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +1 -1
  314. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +1 -1
  315. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +1 -1
  316. nautobot/users/api/serializers.py +1 -0
  317. nautobot/virtualization/filters.py +19 -2
  318. nautobot/virtualization/tests/test_filters.py +9 -0
  319. {nautobot-2.3.12.dist-info → nautobot-2.3.14.dist-info}/METADATA +3 -3
  320. {nautobot-2.3.12.dist-info → nautobot-2.3.14.dist-info}/RECORD +324 -324
  321. {nautobot-2.3.12.dist-info → nautobot-2.3.14.dist-info}/LICENSE.txt +0 -0
  322. {nautobot-2.3.12.dist-info → nautobot-2.3.14.dist-info}/NOTICE +0 -0
  323. {nautobot-2.3.12.dist-info → nautobot-2.3.14.dist-info}/WHEEL +0 -0
  324. {nautobot-2.3.12.dist-info → nautobot-2.3.14.dist-info}/entry_points.txt +0 -0
nautobot/ipam/lookups.py CHANGED
@@ -3,7 +3,9 @@ from django.db.models import Lookup, lookups
3
3
  import netaddr
4
4
 
5
5
 
6
- def _mysql_varbin_to_broadcast():
6
+ def _mysql_varbin_to_broadcast(alias=None):
7
+ if alias:
8
+ return f"HEX({alias}.broadcast)"
7
9
  return "HEX(broadcast)"
8
10
 
9
11
 
@@ -13,11 +15,15 @@ def _mysql_varbin_to_hex(lhs, alias=None):
13
15
  return f"HEX({lhs})"
14
16
 
15
17
 
16
- def _mysql_varbin_to_network():
18
+ def _mysql_varbin_to_network(alias=None):
19
+ if alias:
20
+ return f"HEX({alias}.network)"
17
21
  return "HEX(network)"
18
22
 
19
23
 
20
- def _postgresql_varbin_to_broadcast(length):
24
+ def _postgresql_varbin_to_broadcast(length, alias=None):
25
+ if alias:
26
+ return f"right({alias}.broadcast::text, -1)::varbit::bit({length})"
21
27
  return f"right(broadcast::text, -1)::varbit::bit({length})"
22
28
 
23
29
 
@@ -27,8 +33,10 @@ def _postgresql_varbin_to_integer(lhs, length, alias=None):
27
33
  return f"right({lhs}::text, -1)::varbit::bit({length})"
28
34
 
29
35
 
30
- def _postgresql_varbin_to_network(lhs, length):
36
+ def _postgresql_varbin_to_network(lhs, length, alias=None):
31
37
  # convert to bitstring, 0 out everything larger than prefix_length
38
+ if alias:
39
+ return f"lpad(right({alias}.{lhs}::text, -1)::varbit::text, {alias}.prefix_length, '0')::bit({length})"
32
40
  return f"lpad(right({lhs}::text, -1)::varbit::text, prefix_length, '0')::bit({length})"
33
41
 
34
42
 
@@ -52,8 +60,8 @@ def get_ip_info(field_name, ip_str, alias=None):
52
60
  ip_details.rhs = py_to_hex(ip.ip, ip_details.length)
53
61
  ip_details.net_addr = f"'{py_to_hex(ip.network, ip_details.length)}'"
54
62
  ip_details.bcast_addr = f"'{py_to_hex(ip[-1], ip_details.length)}'"
55
- ip_details.q_net = _mysql_varbin_to_network()
56
- ip_details.q_bcast = _mysql_varbin_to_broadcast()
63
+ ip_details.q_net = _mysql_varbin_to_network(alias=alias)
64
+ ip_details.q_bcast = _mysql_varbin_to_broadcast(alias=alias)
57
65
  ip_details.q_ip = _mysql_varbin_to_hex(field_name, alias=alias)
58
66
 
59
67
  elif _connection.vendor == "postgresql":
@@ -61,8 +69,8 @@ def get_ip_info(field_name, ip_str, alias=None):
61
69
  ip_details.addr_str = f"B'{bin(int(ip_details.addr))[2:].zfill(ip_details.length)}'"
62
70
  ip_details.net_addr = f"B'{bin(int(ip.network))[2:].zfill(ip_details.length)}'"
63
71
  ip_details.bcast_addr = f"B'{bin(int(ip[-1]))[2:].zfill(ip_details.length)}'"
64
- ip_details.q_net = _postgresql_varbin_to_network(field_name, ip_details.length)
65
- ip_details.q_bcast = _postgresql_varbin_to_broadcast(ip_details.length)
72
+ ip_details.q_net = _postgresql_varbin_to_network(field_name, ip_details.length, alias=alias)
73
+ ip_details.q_bcast = _postgresql_varbin_to_broadcast(ip_details.length, alias=alias)
66
74
  ip_details.q_ip = _postgresql_varbin_to_integer(field_name, ip_details.length, alias=alias)
67
75
 
68
76
  return ip_details
@@ -71,25 +79,38 @@ def get_ip_info(field_name, ip_str, alias=None):
71
79
  class IPDetails:
72
80
  """Class for setting up all details about an IP they may be needed"""
73
81
 
74
- net = None
75
- addr = None
76
- ip = None
77
- prefix = None
78
- length = None
79
- addr_str = None
80
- rhs = None
81
- net_addr = None
82
- bcast_addr = None
83
- q_net = None
84
- q_bcast = None
85
- q_ip = None
82
+ addr = None # 10.0.0.0
83
+ ip = None # 10.0.0.0/8
84
+ prefix = None # 8
85
+ length = None # 32
86
+ addr_str = None # B'00001010000000000000000000000000'
87
+ rhs = None # 00001010000000000000000000000000
88
+ net_addr = None # B'00001010000000000000000000000000'
89
+ bcast_addr = None # B'00001010111111111111111111111111'
90
+ q_net = None # mysql or postgres specific
91
+ q_bcast = None # mysql or postgres specific
92
+ q_ip = None # mysql or postgres specific
86
93
  to_len = {4: 32, 6: 128}
87
94
 
95
+ def __str__(self):
96
+ return f"""\
97
+ addr: {self.addr}
98
+ ip: {self.ip}
99
+ prefix: {self.prefix}
100
+ length: {self.length}
101
+ addr_str: {self.addr_str}
102
+ rhs: {self.rhs}
103
+ net_addr: {self.net_addr}
104
+ bcast_addr: {self.bcast_addr}
105
+ q_net: {self.q_net}
106
+ q_bcast: {self.q_bcast}
107
+ q_ip: {self.q_ip}"""
108
+
88
109
 
89
110
  class StringMatchMixin:
90
- def process_lhs(self, qn, connection, lhs=None):
111
+ def process_lhs(self, compiler, connection, lhs=None):
91
112
  lhs = lhs or self.lhs
92
- lhs_string, lhs_params = qn.compile(lhs)
113
+ lhs_string, lhs_params = compiler.compile(lhs)
93
114
  if connection.vendor == "postgresql":
94
115
  raise NotSupportedError("Lookup not supported on postgresql.")
95
116
  return f"INET6_NTOA({lhs_string})", lhs_params
@@ -129,6 +150,7 @@ class IRegex(StringMatchMixin, lookups.IRegex):
129
150
 
130
151
  class NetworkFieldMixin:
131
152
  def get_prep_lookup(self):
153
+ self.alias = self.lhs.alias
132
154
  field_name = self.lhs.field.name
133
155
  if field_name not in ["host", "network"]:
134
156
  raise NotSupportedError(f"Lookup only provided on the host and network fields, not {field_name}.")
@@ -139,8 +161,8 @@ class NetworkFieldMixin:
139
161
  self.ip = get_ip_info(field_name, self.rhs, alias=self.lhs.alias)
140
162
  return str(self.ip.ip)
141
163
 
142
- def process_rhs(self, qn, connection):
143
- sql, params = super().process_rhs(qn, connection)
164
+ def process_rhs(self, compiler, connection):
165
+ sql, params = super().process_rhs(compiler, connection)
144
166
  params[0] = self.ip.rhs
145
167
  return sql, params
146
168
 
@@ -148,50 +170,67 @@ class NetworkFieldMixin:
148
170
  class NetEquals(NetworkFieldMixin, Lookup):
149
171
  lookup_name = "net_equals"
150
172
 
151
- def as_sql(self, qn, connection):
152
- _, lhs_params = self.process_lhs(qn, connection)
153
- rhs, rhs_params = self.process_rhs(qn, connection)
154
- query = f"prefix_length = {self.ip.prefix} AND {rhs} = {self.ip.q_ip}"
173
+ def as_sql(self, compiler, connection):
174
+ _, lhs_params = self.process_lhs(compiler, connection)
175
+ rhs, rhs_params = self.process_rhs(compiler, connection)
176
+ if self.alias:
177
+ query = f"{self.alias}.prefix_length = {self.ip.prefix} AND {rhs} = {self.ip.q_ip}"
178
+ else:
179
+ query = f"prefix_length = {self.ip.prefix} AND {rhs} = {self.ip.q_ip}"
155
180
  return query, lhs_params + rhs_params
156
181
 
157
182
 
158
183
  class NetContainsOrEquals(NetworkFieldMixin, Lookup):
159
184
  lookup_name = "net_contains_or_equals"
160
185
 
161
- def as_sql(self, qn, connection):
162
- _, lhs_params = self.process_lhs(qn, connection)
163
- rhs, rhs_params = self.process_rhs(qn, connection)
164
- query = f"prefix_length <= {self.ip.prefix} AND {rhs} BETWEEN {self.ip.q_net} AND {self.ip.q_bcast}"
186
+ def as_sql(self, compiler, connection):
187
+ _, lhs_params = self.process_lhs(compiler, connection)
188
+ rhs, rhs_params = self.process_rhs(compiler, connection)
189
+ if self.alias:
190
+ query = f"{self.alias}.prefix_length <= {self.ip.prefix} AND {rhs} BETWEEN {self.ip.q_net} AND {self.ip.q_bcast}"
191
+ else:
192
+ query = f"prefix_length <= {self.ip.prefix} AND {rhs} BETWEEN {self.ip.q_net} AND {self.ip.q_bcast}"
165
193
  return query, lhs_params + rhs_params
166
194
 
167
195
 
168
196
  class NetContains(NetworkFieldMixin, Lookup):
169
197
  lookup_name = "net_contains"
170
198
 
171
- def as_sql(self, qn, connection):
172
- _, lhs_params = self.process_lhs(qn, connection)
173
- rhs, rhs_params = self.process_rhs(qn, connection)
174
- query = f"prefix_length < {self.ip.prefix} AND {rhs} BETWEEN {self.ip.q_net} AND {self.ip.q_bcast}"
199
+ def as_sql(self, compiler, connection):
200
+ _, lhs_params = self.process_lhs(compiler, connection)
201
+ rhs, rhs_params = self.process_rhs(compiler, connection)
202
+ if self.alias:
203
+ query = (
204
+ f"{self.alias}.prefix_length < {self.ip.prefix} AND {rhs} BETWEEN {self.ip.q_net} AND {self.ip.q_bcast}"
205
+ )
206
+ else:
207
+ query = f"prefix_length < {self.ip.prefix} AND {rhs} BETWEEN {self.ip.q_net} AND {self.ip.q_bcast}"
175
208
  return query, lhs_params + rhs_params
176
209
 
177
210
 
178
211
  class NetContainedOrEqual(NetworkFieldMixin, Lookup):
179
212
  lookup_name = "net_contained_or_equal"
180
213
 
181
- def as_sql(self, qn, connection):
182
- _, lhs_params = self.process_lhs(qn, connection)
183
- rhs, rhs_params = self.process_rhs(qn, connection)
184
- query = f"prefix_length >= {self.ip.prefix} AND {self.ip.q_net} BETWEEN {rhs} AND {self.ip.bcast_addr}"
214
+ def as_sql(self, compiler, connection):
215
+ _, lhs_params = self.process_lhs(compiler, connection)
216
+ rhs, rhs_params = self.process_rhs(compiler, connection)
217
+ if self.alias:
218
+ query = f"{self.alias}.prefix_length >= {self.ip.prefix} AND {self.ip.q_net} BETWEEN {rhs} AND {self.ip.bcast_addr}"
219
+ else:
220
+ query = f"prefix_length >= {self.ip.prefix} AND {self.ip.q_net} BETWEEN {rhs} AND {self.ip.bcast_addr}"
185
221
  return query, lhs_params + rhs_params
186
222
 
187
223
 
188
224
  class NetContained(NetworkFieldMixin, Lookup):
189
225
  lookup_name = "net_contained"
190
226
 
191
- def as_sql(self, qn, connection):
192
- _, lhs_params = self.process_lhs(qn, connection)
193
- rhs, rhs_params = self.process_rhs(qn, connection)
194
- query = f"prefix_length > {self.ip.prefix} AND {self.ip.q_net} BETWEEN {rhs} AND {self.ip.bcast_addr}"
227
+ def as_sql(self, compiler, connection):
228
+ _, lhs_params = self.process_lhs(compiler, connection)
229
+ rhs, rhs_params = self.process_rhs(compiler, connection)
230
+ if self.alias:
231
+ query = f"{self.alias}.prefix_length > {self.ip.prefix} AND {self.ip.q_net} BETWEEN {rhs} AND {self.ip.bcast_addr}"
232
+ else:
233
+ query = f"prefix_length > {self.ip.prefix} AND {self.ip.q_net} BETWEEN {rhs} AND {self.ip.bcast_addr}"
195
234
  return query, lhs_params + rhs_params
196
235
 
197
236
 
@@ -205,19 +244,19 @@ class NetHost(Lookup):
205
244
  self.ip = get_ip_info(field_name, self.rhs, alias=self.lhs.alias)
206
245
  return str(self.ip.ip)
207
246
 
208
- def process_rhs(self, qn, connection):
209
- sql, params = super().process_rhs(qn, connection)
247
+ def process_rhs(self, compiler, connection):
248
+ sql, params = super().process_rhs(compiler, connection)
210
249
  params[0] = self.ip.rhs
211
250
  return sql, params
212
251
 
213
- def process_lhs(self, qn, connection, lhs=None):
252
+ def process_lhs(self, compiler, connection, lhs=None):
214
253
  lhs = lhs or self.lhs
215
- _, lhs_params = qn.compile(lhs)
254
+ _, lhs_params = compiler.compile(lhs)
216
255
  return self.ip.q_ip, lhs_params
217
256
 
218
- def as_sql(self, qn, connection):
219
- lhs, lhs_params = self.process_lhs(qn, connection)
220
- rhs, rhs_params = self.process_rhs(qn, connection)
257
+ def as_sql(self, compiler, connection):
258
+ lhs, lhs_params = self.process_lhs(compiler, connection)
259
+ rhs, rhs_params = self.process_rhs(compiler, connection)
221
260
  return f"{lhs} = {rhs}", lhs_params + rhs_params
222
261
 
223
262
 
@@ -242,9 +281,9 @@ class NetIn(Lookup):
242
281
  self.query_starter = "'1' != ANY(%s) AND "
243
282
  return self.rhs
244
283
 
245
- def as_sql(self, qn, connection):
246
- _, lhs_params = self.process_lhs(qn, connection)
247
- _, rhs_params = self.process_rhs(qn, connection)
284
+ def as_sql(self, compiler, connection):
285
+ _, lhs_params = self.process_lhs(compiler, connection)
286
+ _, rhs_params = self.process_rhs(compiler, connection)
248
287
  query = self.query_starter
249
288
  query += "OR ".join(f"{ip.q_ip} BETWEEN {ip.net_addr} AND {ip.bcast_addr} " for ip in self.ips)
250
289
  return query, lhs_params + rhs_params
@@ -253,9 +292,9 @@ class NetIn(Lookup):
253
292
  class NetHostContained(NetworkFieldMixin, Lookup):
254
293
  lookup_name = "net_host_contained"
255
294
 
256
- def as_sql(self, qn, connection):
257
- _, lhs_params = self.process_lhs(qn, connection)
258
- rhs, rhs_params = self.process_rhs(qn, connection)
295
+ def as_sql(self, compiler, connection):
296
+ _, lhs_params = self.process_lhs(compiler, connection)
297
+ rhs, rhs_params = self.process_rhs(compiler, connection)
259
298
  query = f"{self.ip.q_ip} BETWEEN {rhs} AND {self.ip.bcast_addr}"
260
299
  return query, lhs_params + rhs_params
261
300
 
@@ -270,12 +309,12 @@ class NetFamily(Lookup):
270
309
  self.rhs = 16
271
310
  return self.rhs
272
311
 
273
- def process_lhs(self, qn, connection, lhs=None):
312
+ def process_lhs(self, compiler, connection, lhs=None):
274
313
  lhs = lhs or self.lhs
275
- lhs_string, lhs_params = qn.compile(lhs)
314
+ lhs_string, lhs_params = compiler.compile(lhs)
276
315
  return f"LENGTH({lhs_string})", lhs_params
277
316
 
278
- def as_sql(self, qn, connection):
279
- lhs, lhs_params = self.process_lhs(qn, connection)
280
- rhs, rhs_params = self.process_rhs(qn, connection)
317
+ def as_sql(self, compiler, connection):
318
+ lhs, lhs_params = self.process_lhs(compiler, connection)
319
+ rhs, rhs_params = self.process_rhs(compiler, connection)
281
320
  return f"{lhs} = {rhs}", lhs_params + rhs_params
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",
@@ -442,13 +442,27 @@ class IPAddressTable(StatusTableMixin, RoleTableMixin, BaseTable):
442
442
  )
443
443
  tenant = TenantColumn()
444
444
  parent__namespace = tables.Column(linkify=True)
445
- # Interface, Device, and VirtualMachine tables aren't currently filterable by IP address (?) so no LinkedCountColumn
446
- interface_count = tables.Column(verbose_name="Interfaces")
447
- interface_parent_count = tables.Column(verbose_name="Devices")
445
+ interface_count = LinkedCountColumn(
446
+ viewname="dcim:interface_list", url_params={"ip_addresses": "pk"}, verbose_name="Interfaces"
447
+ )
448
+ # TODO: what about interfaces assigned to modules?
449
+ interface_parent_count = LinkedCountColumn(
450
+ viewname="dcim:device_list",
451
+ url_params={"ip_addresses": "pk"},
452
+ reverse_lookup="interfaces__ip_addresses",
453
+ distinct=True,
454
+ verbose_name="Devices",
455
+ )
448
456
  vm_interface_count = LinkedCountColumn(
449
457
  viewname="virtualization:vminterface_list", url_params={"ip_addresses": "pk"}, verbose_name="VM Interfaces"
450
458
  )
451
- vm_interface_parent_count = tables.Column(verbose_name="Virtual Machines")
459
+ vm_interface_parent_count = LinkedCountColumn(
460
+ viewname="virtualization:virtualmachine_list",
461
+ url_params={"ip_addresses": "pk"},
462
+ reverse_lookup="interfaces__ip_addresses",
463
+ distinct=True,
464
+ verbose_name="Virtual Machines",
465
+ )
452
466
 
453
467
  class Meta(BaseTable.Meta):
454
468
  model = IPAddress
@@ -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(