nautobot 2.4.16__py3-none-any.whl → 2.4.17__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 (370) hide show
  1. nautobot/apps/utils.py +2 -0
  2. nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +3 -3
  3. nautobot/cloud/views.py +7 -0
  4. nautobot/core/apps/__init__.py +1 -0
  5. nautobot/core/celery/__init__.py +2 -1
  6. nautobot/core/templates/components/panel/panel.html +1 -1
  7. nautobot/core/templates/inc/paginator.html +3 -3
  8. nautobot/core/templates/inc/table.html +2 -2
  9. nautobot/core/templatetags/helpers.py +80 -6
  10. nautobot/core/testing/mixins.py +1 -1
  11. nautobot/core/testing/views.py +2 -4
  12. nautobot/core/ui/bulk_buttons.py +53 -53
  13. nautobot/core/ui/object_detail.py +9 -4
  14. nautobot/core/utils/data.py +13 -0
  15. nautobot/core/utils/deprecation.py +2 -0
  16. nautobot/dcim/migrations/0073_alter_powerport_power_factor_and_more.py +41 -0
  17. nautobot/dcim/models/device_component_templates.py +4 -2
  18. nautobot/dcim/models/device_components.py +3 -2
  19. nautobot/dcim/templates/dcim/rack_elevation_list.html +4 -4
  20. nautobot/dcim/views.py +9 -0
  21. nautobot/extras/models/customfields.py +45 -9
  22. nautobot/extras/templates/extras/configcontext_retrieve.html +1 -1
  23. nautobot/extras/templates/extras/configcontext_update.html +49 -49
  24. nautobot/extras/templates/extras/configcontextschema_retrieve.html +47 -47
  25. nautobot/extras/templates/extras/configcontextschema_update.html +18 -18
  26. nautobot/extras/templates/extras/inc/job_table.html +1 -1
  27. nautobot/extras/templates/extras/inc/object_contact_header.html +2 -2
  28. nautobot/extras/templates/extras/note_retrieve.html +53 -53
  29. nautobot/extras/templates/extras/tag_retrieve.html +1 -1
  30. nautobot/extras/templates/extras/tag_update.html +14 -14
  31. nautobot/extras/templates/extras/team_retrieve.html +1 -1
  32. nautobot/extras/tests/test_models.py +216 -0
  33. nautobot/extras/tests/test_views.py +2 -2
  34. nautobot/extras/views.py +1 -0
  35. nautobot/ipam/apps.py +1 -0
  36. nautobot/ipam/jobs/__init__.py +10 -0
  37. nautobot/ipam/jobs/cleanup.py +296 -0
  38. nautobot/ipam/models.py +301 -178
  39. nautobot/ipam/templates/ipam/inc/ipadress_edit_header.html +3 -3
  40. nautobot/ipam/templates/ipam/inc/toggle_available.html +2 -2
  41. nautobot/ipam/templates/ipam/ipaddress_assign.html +1 -1
  42. nautobot/ipam/templates/ipam/prefix_list.html +1 -1
  43. nautobot/ipam/templates/ipam/vlan_retrieve.html +1 -77
  44. nautobot/ipam/tests/test_jobs.py +454 -0
  45. nautobot/ipam/tests/test_models.py +290 -122
  46. nautobot/ipam/tests/test_views.py +40 -164
  47. nautobot/ipam/urls.py +0 -11
  48. nautobot/ipam/utils/testing.py +9 -4
  49. nautobot/ipam/views.py +166 -235
  50. nautobot/project-static/docs/404.html +9 -6
  51. nautobot/project-static/docs/apps/index.html +9 -6
  52. nautobot/project-static/docs/apps/nautobot-apps.html +9 -6
  53. nautobot/project-static/docs/assets/javascripts/bundle.92b07e13.min.js +16 -0
  54. nautobot/project-static/docs/assets/javascripts/{bundle.50899def.min.js.map → bundle.92b07e13.min.js.map} +2 -2
  55. nautobot/project-static/docs/assets/javascripts/workers/{search.d50fe291.min.js → search.973d3a69.min.js} +4 -4
  56. nautobot/project-static/docs/assets/javascripts/workers/{search.d50fe291.min.js.map → search.973d3a69.min.js.map} +1 -1
  57. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +9 -6
  58. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +9 -6
  59. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +9 -6
  60. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +9 -6
  61. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +10 -7
  62. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +9 -6
  63. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +9 -6
  64. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +9 -6
  65. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +9 -6
  66. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +9 -6
  67. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +9 -6
  68. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +9 -6
  69. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +9 -6
  70. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +9 -6
  71. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +9 -6
  72. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +11 -8
  73. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +9 -6
  74. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +9 -6
  75. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +11 -8
  76. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +9 -6
  77. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +28 -9
  78. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +9 -6
  79. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +69 -7
  80. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +9 -6
  81. nautobot/project-static/docs/development/apps/api/configuration-view.html +13 -10
  82. nautobot/project-static/docs/development/apps/api/database-backend-config.html +11 -8
  83. nautobot/project-static/docs/development/apps/api/models/django-admin.html +13 -10
  84. nautobot/project-static/docs/development/apps/api/models/global-search.html +10 -7
  85. nautobot/project-static/docs/development/apps/api/models/graphql.html +18 -15
  86. nautobot/project-static/docs/development/apps/api/models/index.html +14 -11
  87. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +11 -8
  88. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +15 -12
  89. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +9 -6
  90. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +15 -12
  91. nautobot/project-static/docs/development/apps/api/platform-features/index.html +9 -6
  92. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +11 -8
  93. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +16 -13
  94. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +12 -10305
  95. nautobot/project-static/docs/development/apps/api/platform-features/prepopulating-data.html +10722 -0
  96. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +15 -12
  97. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +14 -11
  98. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +9 -6
  99. nautobot/project-static/docs/development/apps/api/prometheus.html +15 -12
  100. nautobot/project-static/docs/development/apps/api/setup.html +9 -6
  101. nautobot/project-static/docs/development/apps/api/testing.html +9 -6
  102. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +12 -9
  103. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +9 -6
  104. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +9 -6
  105. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +9 -6
  106. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +20 -17
  107. nautobot/project-static/docs/development/apps/api/views/base-template.html +9 -6
  108. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +15 -12
  109. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +14 -11
  110. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +9 -6
  111. nautobot/project-static/docs/development/apps/api/views/index.html +9 -6
  112. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +10 -7
  113. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +24 -21
  114. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +12 -9
  115. nautobot/project-static/docs/development/apps/api/views/notes.html +10 -7
  116. nautobot/project-static/docs/development/apps/api/views/rest-api.html +19 -16
  117. nautobot/project-static/docs/development/apps/api/views/urls.html +11 -8
  118. nautobot/project-static/docs/development/apps/index.html +9 -6
  119. nautobot/project-static/docs/development/apps/migration/code-updates.html +19 -16
  120. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +9 -6
  121. nautobot/project-static/docs/development/apps/migration/from-v1.html +9 -6
  122. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +22 -19
  123. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +9 -6
  124. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +9 -6
  125. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +9 -6
  126. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +9 -6
  127. nautobot/project-static/docs/development/apps/migration/ui-component-framework/breadcrumbs-titles.html +14 -11
  128. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +27 -24
  129. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +20 -17
  130. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +20 -17
  131. nautobot/project-static/docs/development/apps/porting-from-netbox.html +9 -6
  132. nautobot/project-static/docs/development/core/application-registry.html +23 -20
  133. nautobot/project-static/docs/development/core/best-practices.html +23 -20
  134. nautobot/project-static/docs/development/core/bootstrap-ui.html +9 -6
  135. nautobot/project-static/docs/development/core/caching.html +9 -6
  136. nautobot/project-static/docs/development/core/controllers.html +9 -6
  137. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +10 -7
  138. nautobot/project-static/docs/development/core/generic-views.html +9 -6
  139. nautobot/project-static/docs/development/core/getting-started.html +9 -6
  140. nautobot/project-static/docs/development/core/homepage.html +12 -9
  141. nautobot/project-static/docs/development/core/index.html +9 -6
  142. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +9 -6
  143. nautobot/project-static/docs/development/core/model-checklist.html +9 -6
  144. nautobot/project-static/docs/development/core/model-features.html +11 -8
  145. nautobot/project-static/docs/development/core/natural-keys.html +21 -18
  146. nautobot/project-static/docs/development/core/navigation-menu.html +10 -7
  147. nautobot/project-static/docs/development/core/release-checklist.html +9 -6
  148. nautobot/project-static/docs/development/core/role-internals.html +9 -6
  149. nautobot/project-static/docs/development/core/settings.html +9 -6
  150. nautobot/project-static/docs/development/core/style-guide.html +32 -29
  151. nautobot/project-static/docs/development/core/templates.html +9 -6
  152. nautobot/project-static/docs/development/core/testing.html +10 -7
  153. nautobot/project-static/docs/development/core/ui-component-framework.html +36 -33
  154. nautobot/project-static/docs/development/core/user-preferences.html +9 -6
  155. nautobot/project-static/docs/development/index.html +9 -6
  156. nautobot/project-static/docs/development/jobs/getting-started.html +13 -10
  157. nautobot/project-static/docs/development/jobs/index.html +9 -6
  158. nautobot/project-static/docs/development/jobs/installation.html +23 -20
  159. nautobot/project-static/docs/development/jobs/job-extensions.html +25 -22
  160. nautobot/project-static/docs/development/jobs/job-logging.html +12 -9
  161. nautobot/project-static/docs/development/jobs/job-patterns.html +45 -42
  162. nautobot/project-static/docs/development/jobs/job-structure.html +53 -50
  163. nautobot/project-static/docs/development/jobs/migration/from-v1.html +23 -20
  164. nautobot/project-static/docs/development/jobs/testing.html +14 -11
  165. nautobot/project-static/docs/index.html +9 -6
  166. nautobot/project-static/docs/objects.inv +0 -0
  167. nautobot/project-static/docs/overview/application_stack.html +9 -6
  168. nautobot/project-static/docs/overview/design_philosophy.html +9 -6
  169. nautobot/project-static/docs/release-notes/index.html +9 -6
  170. nautobot/project-static/docs/release-notes/version-1.0.html +9 -6
  171. nautobot/project-static/docs/release-notes/version-1.1.html +9 -6
  172. nautobot/project-static/docs/release-notes/version-1.2.html +10 -7
  173. nautobot/project-static/docs/release-notes/version-1.3.html +9 -6
  174. nautobot/project-static/docs/release-notes/version-1.4.html +9 -6
  175. nautobot/project-static/docs/release-notes/version-1.5.html +13 -10
  176. nautobot/project-static/docs/release-notes/version-1.6.html +9 -6
  177. nautobot/project-static/docs/release-notes/version-2.0.html +9 -6
  178. nautobot/project-static/docs/release-notes/version-2.1.html +9 -6
  179. nautobot/project-static/docs/release-notes/version-2.2.html +9 -6
  180. nautobot/project-static/docs/release-notes/version-2.3.html +9 -6
  181. nautobot/project-static/docs/release-notes/version-2.4.html +267 -6
  182. nautobot/project-static/docs/requirements.txt +2 -2
  183. nautobot/project-static/docs/search/search_index.json +1 -1
  184. nautobot/project-static/docs/sitemap.xml +301 -301
  185. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  186. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +15 -12
  187. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +9 -6
  188. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +16 -13
  189. nautobot/project-static/docs/user-guide/administration/configuration/index.html +9 -6
  190. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +9 -6
  191. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +11 -8
  192. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +9 -6
  193. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +9 -6
  194. nautobot/project-static/docs/user-guide/administration/guides/docker.html +9 -6
  195. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +9 -6
  196. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +9 -6
  197. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +9 -6
  198. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +9 -6
  199. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +9 -6
  200. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +16 -13
  201. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +9 -6
  202. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +9 -6
  203. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +9 -6
  204. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +9 -6
  205. nautobot/project-static/docs/user-guide/administration/installation/index.html +9 -6
  206. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +9 -6
  207. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +9 -6
  208. nautobot/project-static/docs/user-guide/administration/installation/services.html +12 -9
  209. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +13 -10
  210. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +10 -7
  211. nautobot/project-static/docs/user-guide/administration/security/index.html +9 -6
  212. nautobot/project-static/docs/user-guide/administration/security/notices.html +9 -6
  213. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +9 -6
  214. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +10 -7
  215. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +9 -6
  216. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +9 -6
  217. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +9 -6
  218. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +9 -6
  219. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +9 -6
  220. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +9 -6
  221. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +9 -6
  222. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +15 -12
  223. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +9 -6
  224. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +9 -6
  225. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +9 -6
  226. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +9 -6
  227. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +9 -6
  228. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +9 -6
  229. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +9 -6
  230. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +9 -6
  231. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +9 -6
  232. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +9 -6
  233. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +9 -6
  234. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +9 -6
  235. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +9 -6
  236. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +9 -6
  237. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +9 -6
  238. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +9 -6
  239. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +9 -6
  240. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +9 -6
  241. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +9 -6
  242. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +9 -6
  243. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +9 -6
  244. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +9 -6
  245. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +9 -6
  246. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +9 -6
  247. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +13 -10
  248. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +9 -6
  249. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +9 -6
  250. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +9 -6
  251. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +9 -6
  252. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +9 -6
  253. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +9 -6
  254. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +9 -6
  255. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +9 -6
  256. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +9 -6
  257. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +9 -6
  258. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +9 -6
  259. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +9 -6
  260. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +9 -6
  261. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +9 -6
  262. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +9 -6
  263. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +9 -6
  264. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +9 -6
  265. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +9 -6
  266. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +9 -6
  267. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +9 -6
  268. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +9 -6
  269. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +9 -6
  270. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +9 -6
  271. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +9 -6
  272. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +9 -6
  273. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +9 -6
  274. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +9 -6
  275. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +9 -6
  276. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +9 -6
  277. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +9 -6
  278. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +9 -6
  279. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +9 -6
  280. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +9 -6
  281. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +11 -8
  282. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +11 -8
  283. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +41 -41
  284. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +9 -6
  285. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +197 -54
  286. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +9 -6
  287. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +9 -6
  288. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +9 -6
  289. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +9 -6
  290. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +9 -6
  291. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +9 -6
  292. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +9 -6
  293. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +9 -6
  294. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +9 -6
  295. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +9 -6
  296. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +9 -6
  297. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +9 -6
  298. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +9 -6
  299. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +9 -6
  300. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +9 -6
  301. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +9 -6
  302. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +9 -6
  303. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +9 -6
  304. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +9 -6
  305. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +9 -6
  306. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +9 -6
  307. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +9 -6
  308. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +9 -6
  309. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +9 -6
  310. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +9 -6
  311. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +9 -6
  312. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +9 -6
  313. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +9 -6
  314. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +9 -6
  315. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +9 -6
  316. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +13 -10
  317. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +9 -6
  318. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +9 -6
  319. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +9 -6
  320. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +9 -6
  321. nautobot/project-static/docs/user-guide/index.html +9 -6
  322. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +9 -6
  323. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +9 -6
  324. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +10 -7
  325. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +9 -6
  326. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +9 -6
  327. nautobot/project-static/docs/user-guide/platform-functionality/events.html +11 -8
  328. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +9 -6
  329. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +9 -6
  330. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +9 -6
  331. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +9 -6
  332. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +9 -6
  333. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +9 -6
  334. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +9 -6
  335. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +9 -6
  336. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +9 -6
  337. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +9 -6
  338. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +9 -6
  339. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +9 -6
  340. nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +9 -6
  341. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +9 -6
  342. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +9 -6
  343. nautobot/project-static/docs/user-guide/platform-functionality/note.html +9 -6
  344. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +12 -9
  345. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +9 -6
  346. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +9 -6
  347. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +9 -6
  348. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +9 -6
  349. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +9 -6
  350. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +9 -6
  351. nautobot/project-static/docs/user-guide/platform-functionality/role.html +9 -6
  352. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9 -6
  353. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +11 -8
  354. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +9 -6
  355. nautobot/project-static/docs/user-guide/platform-functionality/status.html +9 -6
  356. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +9 -6
  357. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +9 -6
  358. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +9 -6
  359. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +9 -6
  360. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +9 -6
  361. nautobot/project-static/fonts/UFL.txt +96 -96
  362. nautobot/project-static/js/forms.js +35 -2
  363. nautobot/virtualization/filters.py +7 -0
  364. {nautobot-2.4.16.dist-info → nautobot-2.4.17.dist-info}/METADATA +6 -6
  365. {nautobot-2.4.16.dist-info → nautobot-2.4.17.dist-info}/RECORD +369 -364
  366. nautobot/project-static/docs/assets/javascripts/bundle.50899def.min.js +0 -16
  367. {nautobot-2.4.16.dist-info → nautobot-2.4.17.dist-info}/LICENSE.txt +0 -0
  368. {nautobot-2.4.16.dist-info → nautobot-2.4.17.dist-info}/NOTICE +0 -0
  369. {nautobot-2.4.16.dist-info → nautobot-2.4.17.dist-info}/WHEEL +0 -0
  370. {nautobot-2.4.16.dist-info → nautobot-2.4.17.dist-info}/entry_points.txt +0 -0
@@ -153,6 +153,10 @@ class ComputedFieldTest(ModelTestCases.BaseModelTestCase):
153
153
  secret_type=SecretsGroupSecretTypeChoices.TYPE_SECRET,
154
154
  )
155
155
 
156
+ # Template strings for validation testing (cannot be saved due to syntax errors)
157
+ self.invalid_template_unclosed_bracket = "{{ obj.name }"
158
+ self.invalid_template_unknown_tag = "{% unknowntag %}{{ obj.name }}{% endunknowntag %}"
159
+
156
160
  def test_render_method(self):
157
161
  rendered_value = self.good_computed_field.render(context={"obj": self.location1})
158
162
  self.assertEqual(rendered_value, f"{self.location1.name} is awesome!")
@@ -214,6 +218,218 @@ class ComputedFieldTest(ModelTestCases.BaseModelTestCase):
214
218
  str(error.exception),
215
219
  )
216
220
 
221
+ def test_template_validation_invalid_syntax(self):
222
+ """
223
+ Test that ComputedField with invalid Jinja2 template syntax raises ValidationError.
224
+ """
225
+ # Invalid template with syntax error - unclosed bracket
226
+ invalid_computed_field = ComputedField(
227
+ label="Invalid Template Test",
228
+ key="invalid_template_test",
229
+ template=self.invalid_template_unclosed_bracket,
230
+ content_type=ContentType.objects.get_for_model(Device),
231
+ )
232
+
233
+ with self.assertRaises(ValidationError) as context:
234
+ invalid_computed_field.full_clean()
235
+
236
+ # Check that the error message contains template-specific information
237
+ error_dict = context.exception.error_dict
238
+ self.assertIn("template", error_dict)
239
+ self.assertIn("Template syntax error", str(error_dict["template"][0]))
240
+ self.assertIn("line", str(error_dict["template"][0]))
241
+
242
+ def test_template_validation_invalid_tag(self):
243
+ """
244
+ Test that ComputedField with invalid Jinja2 tag raises ValidationError.
245
+ """
246
+ # Invalid template with unknown tag
247
+ invalid_computed_field = ComputedField(
248
+ label="Invalid Tag Test",
249
+ key="invalid_tag_test",
250
+ template=self.invalid_template_unknown_tag,
251
+ content_type=ContentType.objects.get_for_model(Device),
252
+ )
253
+
254
+ with self.assertRaises(ValidationError) as context:
255
+ invalid_computed_field.full_clean()
256
+
257
+ # Check that the error message contains template-specific information
258
+ error_dict = context.exception.error_dict
259
+ self.assertIn("template", error_dict)
260
+ self.assertIn("Template syntax error", str(error_dict["template"][0]))
261
+
262
+ def test_bulk_create_valid_templates(self):
263
+ """Test that bulk_create works with valid templates."""
264
+ valid_fields = [
265
+ ComputedField(
266
+ label="Bulk Test 1",
267
+ key="bulk_test_1",
268
+ template="{{ obj.name }} - Test 1",
269
+ content_type=ContentType.objects.get_for_model(Device),
270
+ ),
271
+ ComputedField(
272
+ label="Bulk Test 2",
273
+ key="bulk_test_2",
274
+ template="{{ obj.id }} - Test 2",
275
+ content_type=ContentType.objects.get_for_model(Device),
276
+ ),
277
+ ]
278
+
279
+ # Should not raise ValidationError
280
+ created_fields = ComputedField.objects.bulk_create(valid_fields)
281
+ self.assertEqual(len(created_fields), 2)
282
+
283
+ def test_bulk_create_invalid_templates(self):
284
+ """Test that bulk_create fails with invalid templates and reports all errors."""
285
+ invalid_fields = [
286
+ ComputedField(
287
+ label="Invalid Test 1",
288
+ key="invalid_test_1",
289
+ template=self.invalid_template_unclosed_bracket,
290
+ content_type=ContentType.objects.get_for_model(Device),
291
+ ),
292
+ ComputedField(
293
+ label="Invalid Test 2",
294
+ key="invalid_test_2",
295
+ template=self.invalid_template_unknown_tag,
296
+ content_type=ContentType.objects.get_for_model(Device),
297
+ ),
298
+ ]
299
+
300
+ with self.assertRaises(ValidationError) as context:
301
+ ComputedField.objects.bulk_create(invalid_fields)
302
+
303
+ # Check that both errors are reported
304
+ error_message = str(context.exception)
305
+ self.assertIn("Template validation failed", error_message)
306
+ self.assertIn("Invalid Test 1", error_message)
307
+ self.assertIn("Invalid Test 2", error_message)
308
+
309
+ def test_bulk_update_template_field(self):
310
+ """Test that bulk_update validates templates when template field is updated."""
311
+ # Create valid objects first
312
+ valid_fields = [
313
+ ComputedField(
314
+ label="Update Test 1",
315
+ key="update_test_1",
316
+ template="{{ obj.name }}",
317
+ content_type=ContentType.objects.get_for_model(Device),
318
+ ),
319
+ ComputedField(
320
+ label="Update Test 2",
321
+ key="update_test_2",
322
+ template="{{ obj.id }}",
323
+ content_type=ContentType.objects.get_for_model(Device),
324
+ ),
325
+ ]
326
+ created_fields = ComputedField.objects.bulk_create(valid_fields)
327
+
328
+ # Update with invalid templates
329
+ for field in created_fields:
330
+ field.template = self.invalid_template_unclosed_bracket
331
+
332
+ with self.assertRaises(ValidationError) as context:
333
+ ComputedField.objects.bulk_update(created_fields, ["template"])
334
+
335
+ # Check that validation error occurred
336
+ error_message = str(context.exception)
337
+ self.assertIn("Template validation failed", error_message)
338
+ self.assertIn("Update Test 1", error_message)
339
+ self.assertIn("Update Test 2", error_message)
340
+
341
+ def test_bulk_update_non_template_field(self):
342
+ """Test that bulk_update skips template validation when template field is not updated."""
343
+ # Create a field with invalid template (bypassing validation for this test)
344
+ field = ComputedField(
345
+ label="Non-template Update Test",
346
+ key="non_template_update_test",
347
+ template="{{ obj.name }}", # Start with valid template
348
+ content_type=ContentType.objects.get_for_model(Device),
349
+ )
350
+ field.save()
351
+
352
+ # Manually set invalid template (simulating existing invalid data)
353
+ ComputedField.objects.filter(pk=field.pk).update(template=self.invalid_template_unclosed_bracket)
354
+ field.refresh_from_db()
355
+
356
+ # Update only the label field - should not trigger template validation
357
+ field.label = "Updated Label"
358
+ try:
359
+ ComputedField.objects.bulk_update([field], ["label"])
360
+ except ValidationError:
361
+ self.fail("bulk_update should not validate templates when template field is not being updated")
362
+
363
+ def test_bulk_create_mixed_valid_invalid(self):
364
+ """Test that bulk_create fails when mixing valid and invalid templates."""
365
+ mixed_fields = [
366
+ ComputedField(
367
+ label="Valid Mixed Test",
368
+ key="valid_mixed_test",
369
+ template="{{ obj.name }} - Valid",
370
+ content_type=ContentType.objects.get_for_model(Device),
371
+ ),
372
+ ComputedField(
373
+ label="Invalid Mixed Test",
374
+ key="invalid_mixed_test",
375
+ template=self.invalid_template_unclosed_bracket,
376
+ content_type=ContentType.objects.get_for_model(Device),
377
+ ),
378
+ ]
379
+
380
+ with self.assertRaises(ValidationError) as context:
381
+ ComputedField.objects.bulk_create(mixed_fields)
382
+
383
+ # Check that the invalid object is reported but valid one is not
384
+ error_message = str(context.exception)
385
+ self.assertIn("Template validation failed", error_message)
386
+ self.assertIn("Invalid Mixed Test", error_message)
387
+ # Valid object should not appear in error message
388
+ self.assertNotIn("Valid Mixed Test", error_message)
389
+
390
+ # Verify no objects were created (all-or-nothing behavior)
391
+ self.assertFalse(ComputedField.objects.filter(key="valid_mixed_test").exists())
392
+ self.assertFalse(ComputedField.objects.filter(key="invalid_mixed_test").exists())
393
+
394
+ def test_bulk_update_mixed_valid_invalid(self):
395
+ """Test that bulk_update fails when mixing valid and invalid template updates."""
396
+ # Create valid objects first
397
+ valid_fields = [
398
+ ComputedField(
399
+ label="Valid Update Mixed",
400
+ key="valid_update_mixed",
401
+ template="{{ obj.name }}",
402
+ content_type=ContentType.objects.get_for_model(Device),
403
+ ),
404
+ ComputedField(
405
+ label="Invalid Update Mixed",
406
+ key="invalid_update_mixed",
407
+ template="{{ obj.id }}",
408
+ content_type=ContentType.objects.get_for_model(Device),
409
+ ),
410
+ ]
411
+ created_fields = ComputedField.objects.bulk_create(valid_fields)
412
+
413
+ # Update: one with valid template, one with invalid
414
+ created_fields[0].template = "{{ obj.name }} - Updated Valid" # Valid
415
+ created_fields[1].template = self.invalid_template_unclosed_bracket # Invalid
416
+
417
+ with self.assertRaises(ValidationError) as context:
418
+ ComputedField.objects.bulk_update(created_fields, ["template"])
419
+
420
+ # Check that only the invalid object is reported
421
+ error_message = str(context.exception)
422
+ self.assertIn("Template validation failed", error_message)
423
+ self.assertIn("Invalid Update Mixed", error_message)
424
+ # Valid object should not appear in error message
425
+ self.assertNotIn("Valid Update Mixed", error_message)
426
+
427
+ # Verify templates were not updated (all-or-nothing behavior)
428
+ created_fields[0].refresh_from_db()
429
+ created_fields[1].refresh_from_db()
430
+ self.assertEqual(created_fields[0].template, "{{ obj.name }}") # Original template
431
+ self.assertEqual(created_fields[1].template, "{{ obj.id }}") # Original template
432
+
217
433
 
218
434
  class ConfigContextTest(ModelTestCases.BaseModelTestCase):
219
435
  """
@@ -205,7 +205,7 @@ class ComputedFieldRenderingTestCase(TestCase):
205
205
  def test_view_object_with_computed_field_fallback_value(self):
206
206
  """Ensure that the fallback_value is rendered if the template fails to render."""
207
207
  # Make the template invalid to demonstrate the fallback value
208
- self.computedfield.template = "FOO {{ obj."
208
+ self.computedfield.template = "FOO {{ obj | invalid_filter }}"
209
209
  self.computedfield.validated_save()
210
210
  response = self.client.get(self.location_type.get_absolute_url(), follow=True)
211
211
  self.assertEqual(response.status_code, 200)
@@ -224,7 +224,7 @@ class ComputedFieldRenderingTestCase(TestCase):
224
224
 
225
225
  def test_view_object_with_computed_field_unsafe_fallback_value(self):
226
226
  """Ensure that computed field fallback values can't be used as an XSS vector."""
227
- self.computedfield.template = "FOO {{ obj."
227
+ self.computedfield.template = "FOO {{ obj | invalid_filter }}"
228
228
  self.computedfield.fallback_value = '<script>alert("Hello world!"</script>'
229
229
  self.computedfield.validated_save()
230
230
  response = self.client.get(self.location_type.get_absolute_url(), follow=True)
nautobot/extras/views.py CHANGED
@@ -2879,6 +2879,7 @@ class TagUIViewSet(NautobotUIViewSet):
2879
2879
  table_filter="tag",
2880
2880
  select_related_fields=["content_type"],
2881
2881
  prefetch_related_fields=["content_object"],
2882
+ include_paginator=True,
2882
2883
  ),
2883
2884
  ),
2884
2885
  )
nautobot/ipam/apps.py CHANGED
@@ -19,6 +19,7 @@ class IPAMConfig(NautobotConfig):
19
19
  from graphene_django.converter import convert_django_field, convert_field_to_string
20
20
 
21
21
  from nautobot.ipam.fields import VarbinaryIPField
22
+ import nautobot.ipam.jobs
22
23
  import nautobot.ipam.signals # noqa: F401 # unused-import -- but this import installs the signals
23
24
 
24
25
  # Register VarbinaryIPField to be converted to a string type
@@ -0,0 +1,10 @@
1
+ from nautobot.core.celery import register_jobs
2
+
3
+ from .cleanup import FixIPAMParents
4
+
5
+ name = "System Jobs"
6
+
7
+ jobs = [
8
+ FixIPAMParents,
9
+ ]
10
+ register_jobs(*jobs)
@@ -0,0 +1,296 @@
1
+ from django.core.exceptions import PermissionDenied
2
+ from django.db import models
3
+
4
+ from nautobot.core.choices import ChoiceSet
5
+ from nautobot.extras.jobs import DryRunVar, IPNetworkVar, Job, MultiChoiceVar, ObjectVar
6
+ from nautobot.ipam.models import get_default_namespace_pk, IPAddress, Namespace, Prefix
7
+
8
+ name = "System Jobs"
9
+
10
+
11
+ class CleanupTypes(ChoiceSet):
12
+ IPADDRESS = "ipam.IPAddress"
13
+ PREFIX = "ipam.Prefix"
14
+
15
+ CHOICES = (
16
+ (IPADDRESS, "IP addresses"),
17
+ (PREFIX, "Prefixes"),
18
+ )
19
+
20
+
21
+ class FixIPAMParents(Job):
22
+ cleanup_types = MultiChoiceVar(
23
+ choices=CleanupTypes.CHOICES, required=True, default=[CleanupTypes.IPADDRESS, CleanupTypes.PREFIX]
24
+ )
25
+
26
+ restrict_to_namespace = ObjectVar(
27
+ model=Namespace, required=False, description="Check only records within this namespace"
28
+ )
29
+ restrict_to_network = IPNetworkVar(required=False, description="Check only records within this network")
30
+
31
+ dryrun = DryRunVar()
32
+
33
+ class Meta:
34
+ name = "Check/Fix IPAM Parents"
35
+ description = "Check for and/or fix incorrect 'parent' values on IP Address and/or Prefix records."
36
+ has_sensitive_variables = False
37
+
38
+ def run( # pylint: disable=arguments-differ
39
+ self, *, cleanup_types, restrict_to_namespace=None, restrict_to_network=None, dryrun=False
40
+ ):
41
+ all_relevant_prefixes = Prefix.objects.restrict(self.user, "change")
42
+
43
+ if restrict_to_namespace is not None:
44
+ self.logger.info(
45
+ "Inspecting only records in namespace %s",
46
+ restrict_to_namespace.name,
47
+ extra={"object": restrict_to_namespace},
48
+ )
49
+ all_relevant_prefixes = all_relevant_prefixes.filter(namespace=restrict_to_namespace)
50
+ if restrict_to_network is not None:
51
+ self.logger.info("Inspecting only records that fall within %s", restrict_to_network)
52
+ all_relevant_prefixes = all_relevant_prefixes.net_contained_or_equal(restrict_to_network)
53
+
54
+ if CleanupTypes.PREFIX in cleanup_types:
55
+ if not self.user.has_perm("ipam.change_prefix"):
56
+ self.fail('User "%s" does not have permission to update Prefix records', self.user.username)
57
+ raise PermissionDenied("User does not have update permission for Prefix records")
58
+
59
+ self.logger.info("Inspecting Prefix records...")
60
+
61
+ self.logger.debug("Beginning with a quick check for obviously wrong `parent` values...")
62
+ # 1. Obviously wrong Prefix parents
63
+ # - parent is set but has wrong IP version
64
+ # - parent is set but has wrong namespace
65
+ # - parent is set but its network/broadcast range doesn't contain the given subnet
66
+ prefixes_with_invalid_parents = (
67
+ all_relevant_prefixes.exclude(parent__ip_version=models.F("ip_version"))
68
+ | all_relevant_prefixes.exclude(parent__namespace_id=models.F("namespace_id"))
69
+ | all_relevant_prefixes.filter(parent__network__gt=models.F("network"))
70
+ | all_relevant_prefixes.filter(parent__broadcast__lt=models.F("broadcast"))
71
+ | all_relevant_prefixes.filter(parent__prefix_length__gte=models.F("prefix_length"))
72
+ ).exclude(parent__isnull=True)
73
+
74
+ prefixes_with_invalid_parents = prefixes_with_invalid_parents.select_related("parent")
75
+
76
+ if prefixes_with_invalid_parents.exists():
77
+ fixed_prefixes = []
78
+ for pfx in prefixes_with_invalid_parents:
79
+ candidate_parents = Prefix.objects.all()
80
+ # Preserve namespace
81
+ candidate_parents = candidate_parents.filter(namespace_id=pfx.namespace_id)
82
+ try:
83
+ parent = candidate_parents.get_closest_parent(pfx.prefix, include_self=False)
84
+ except Prefix.DoesNotExist:
85
+ parent = None
86
+ self.logger.warning(
87
+ "Parent for %s should be corrected from %s to %s",
88
+ pfx.prefix,
89
+ pfx.parent.display if pfx.parent is not None else None,
90
+ parent.display if parent is not None else None,
91
+ extra={"object": pfx},
92
+ )
93
+ pfx.parent = parent
94
+ fixed_prefixes.append(pfx)
95
+
96
+ if dryrun:
97
+ self.logger.warning(
98
+ "Would correct invalid `parent` for %d Prefixes if this were not a dry-run", len(fixed_prefixes)
99
+ )
100
+ else:
101
+ update_count = Prefix.objects.bulk_update(fixed_prefixes, ["parent"], batch_size=1000)
102
+ self.logger.success("Corrected invalid `parent` for %d Prefixes", update_count)
103
+ else:
104
+ self.logger.success("No Prefix records had clearly invalid `parent` values")
105
+
106
+ self.logger.debug("Continuing by checking Prefixes with null `parent` to make sure that's correct...")
107
+ # 2. parent is null but should not be
108
+ fixed_prefixes = []
109
+ processed_pfx_count = 0
110
+ for pfx in all_relevant_prefixes.filter(parent__isnull=True):
111
+ candidate_parents = Prefix.objects.all()
112
+ # Preserve namespace
113
+ candidate_parents = candidate_parents.filter(namespace_id=pfx.namespace_id)
114
+ try:
115
+ parent = candidate_parents.get_closest_parent(pfx.prefix, include_self=False)
116
+ except Prefix.DoesNotExist:
117
+ parent = None
118
+
119
+ if parent is not None:
120
+ self.logger.warning(
121
+ "Parent for %s should be set to %s instead of None",
122
+ pfx.display,
123
+ parent.prefix,
124
+ extra={"object": pfx},
125
+ )
126
+ pfx.parent = parent
127
+ fixed_prefixes.append(pfx)
128
+ processed_pfx_count += 1
129
+
130
+ self.logger.debug(
131
+ "Inspected %d Prefixes with null `parent` to see if an appropriate parent exists",
132
+ processed_pfx_count,
133
+ )
134
+
135
+ if fixed_prefixes:
136
+ if dryrun:
137
+ self.logger.warning(
138
+ "Would set a more precise `parent` for %d Prefixes if this were not a dry-run",
139
+ len(fixed_prefixes),
140
+ )
141
+ else:
142
+ update_count = Prefix.objects.bulk_update(fixed_prefixes, ["parent"], batch_size=1000)
143
+ self.logger.success("Corrected imprecise `parent` for %d Prefixes", update_count)
144
+
145
+ self.logger.debug("Continuing with a more involved check for more subtly incorrect `parent` values...")
146
+ # 3. More subtly wrong Prefix parents
147
+ # - parent is set but a more specific parent Prefix also exists
148
+ fixed_prefixes = []
149
+ processed_pfx_count = 0
150
+ for pfx in all_relevant_prefixes.filter(
151
+ parent__prefix_length__lt=models.F("prefix_length") - 1
152
+ ).select_related("parent"):
153
+ try:
154
+ parent = pfx.parent.subnets(include_self=True).get_closest_parent(pfx.prefix, include_self=False)
155
+ except Prefix.DoesNotExist:
156
+ parent = None
157
+ if parent != pfx.parent:
158
+ self.logger.warning(
159
+ "Parent for %s should be corrected from %s to %s",
160
+ pfx.display,
161
+ pfx.parent.prefix if pfx.parent else None,
162
+ parent.prefix if parent else None,
163
+ extra={"object": pfx},
164
+ )
165
+ pfx.parent = parent
166
+ fixed_prefixes.append(pfx)
167
+ processed_pfx_count += 1
168
+
169
+ self.logger.debug(
170
+ "Inspected %d Prefixes for more subtly incorrect `parent` values",
171
+ processed_pfx_count,
172
+ )
173
+
174
+ if fixed_prefixes:
175
+ if dryrun:
176
+ self.logger.warning(
177
+ "Would set a more precise `parent` for %d Prefixes if this were not a dry-run",
178
+ len(fixed_prefixes),
179
+ )
180
+ else:
181
+ update_count = Prefix.objects.bulk_update(fixed_prefixes, ["parent"], batch_size=1000)
182
+ self.logger.success("Corrected imprecise `parent` for %d Prefixes", update_count)
183
+
184
+ if CleanupTypes.IPADDRESS in cleanup_types:
185
+ if not self.user.has_perm("ipam.change_ipaddress"):
186
+ self.fail('User "%s" does not have permission to update IP Address records', self.user.username)
187
+ raise PermissionDenied("User does not have update permission for IP Address records")
188
+
189
+ self.logger.info("Inspecting IP Address records...")
190
+
191
+ all_relevant_ips = IPAddress.objects.restrict(self.user, "change")
192
+ if restrict_to_namespace is not None:
193
+ self.logger.info("Inspecting only records in namespace %s", restrict_to_namespace.name)
194
+ all_relevant_ips = all_relevant_ips.filter(parent__namespace=restrict_to_namespace)
195
+ if restrict_to_network is not None:
196
+ self.logger.info("Inspecting only records that fall within %s", restrict_to_network)
197
+ all_relevant_ips = all_relevant_ips.net_host_contained(restrict_to_network)
198
+
199
+ self.logger.debug("Beginning with a quick check for obviously wrong `parent` values...")
200
+ # 4. Obviously wrong IPAddress parents
201
+ # - parent is unset entirely
202
+ # - parent is set but has wrong IP version
203
+ # - parent is set but its network/broadcast range doesn't contain the given host IP
204
+ ips_with_invalid_parents = (
205
+ all_relevant_ips.filter(parent__isnull=True)
206
+ | all_relevant_ips.exclude(parent__ip_version=models.F("ip_version"))
207
+ | all_relevant_ips.filter(parent__network__gt=models.F("host"))
208
+ | all_relevant_ips.filter(parent__broadcast__lt=models.F("host"))
209
+ )
210
+
211
+ ips_with_invalid_parents = ips_with_invalid_parents.select_related("parent")
212
+
213
+ if ips_with_invalid_parents.exists():
214
+ fixed_ips = []
215
+ for ip in ips_with_invalid_parents:
216
+ candidate_parents = Prefix.objects.all()
217
+ # Preserve namespace
218
+ if ip.parent is not None:
219
+ candidate_parents = candidate_parents.filter(namespace_id=ip.parent.namespace_id)
220
+ else:
221
+ candidate_parents = candidate_parents.filter(namespace_id=get_default_namespace_pk())
222
+ try:
223
+ parent = candidate_parents.get_closest_parent(ip.host, include_self=True)
224
+ self.logger.warning(
225
+ "Parent for %s should be corrected from %s to %s",
226
+ ip.host,
227
+ ip.parent.prefix if ip.parent is not None else None,
228
+ parent.prefix,
229
+ extra={"object": ip},
230
+ )
231
+ ip.parent = parent
232
+ fixed_ips.append(ip)
233
+ except Prefix.DoesNotExist:
234
+ self.logger.warning(
235
+ "No valid parent Prefix could be identified for %s. "
236
+ "You should create a %s/%d Prefix or similar to contain this IP Address.",
237
+ ip.host,
238
+ ip.address.network,
239
+ ip.mask_length,
240
+ extra={"object": ip},
241
+ )
242
+
243
+ if dryrun:
244
+ self.logger.warning(
245
+ "Would correct invalid `parent` for %d IP Addresses if this were not a dry-run", len(fixed_ips)
246
+ )
247
+ else:
248
+ update_count = IPAddress.objects.bulk_update(fixed_ips, ["parent"], batch_size=1000)
249
+ self.logger.success("Corrected invalid `parent` for %d IP Addresses", update_count)
250
+ else:
251
+ self.logger.success("No IP Address records had null or clearly invalid `parent` values")
252
+
253
+ self.logger.debug("Continuing with a more involved check for more subtly incorrect `parent` values...")
254
+ # 5. More subtly wrong IPAddress parents
255
+ # - parent is set and contains the IP, but is not the most specific such Prefix
256
+ fixed_ips = []
257
+ processed_ip_count = 0
258
+ for ip in all_relevant_ips.exclude(parent__children__isnull=True).select_related("parent"):
259
+ candidate_parents = ip.parent.subnets(include_self=True)
260
+ try:
261
+ parent = candidate_parents.get_closest_parent(ip.host, include_self=True)
262
+ if parent.id != ip.parent_id:
263
+ self.logger.warning(
264
+ "Parent for %s should be corrected from %s to %s",
265
+ ip.host,
266
+ ip.parent.prefix,
267
+ parent.prefix,
268
+ extra={"object": ip},
269
+ )
270
+ ip.parent = parent
271
+ fixed_ips.append(ip)
272
+ except Prefix.DoesNotExist:
273
+ self.logger.warning(
274
+ "No valid parent Prefix could be identified for %s. "
275
+ "You should create a %s/%d Prefix or similar to contain this IP Address.",
276
+ ip.host,
277
+ ip.address.network,
278
+ ip.mask_length,
279
+ extra={"object": ip},
280
+ )
281
+ processed_ip_count += 1
282
+
283
+ self.logger.debug(
284
+ "Inspected %d IP Addresses for more subtly incorrect `parent` values",
285
+ processed_ip_count,
286
+ )
287
+
288
+ if fixed_ips:
289
+ if dryrun:
290
+ self.logger.warning(
291
+ "Would set a more precise `parent` for %d IP Addresses if this were not a dry-run",
292
+ len(fixed_ips),
293
+ )
294
+ else:
295
+ update_count = IPAddress.objects.bulk_update(fixed_ips, ["parent"], batch_size=1000)
296
+ self.logger.success("Corrected imprecise `parent` for %d IP Addresses", update_count)