nautobot 2.4.1__py3-none-any.whl → 2.4.3__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 (461) hide show
  1. nautobot/circuits/templates/circuits/inc/circuit_termination.html +1 -1
  2. nautobot/circuits/tests/integration/test_circuit.py +135 -0
  3. nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +43 -0
  4. nautobot/circuits/tests/integration/test_relationships.py +1 -1
  5. nautobot/circuits/views.py +4 -1
  6. nautobot/cloud/api/views.py +3 -3
  7. nautobot/core/apps/__init__.py +0 -5
  8. nautobot/core/constants.py +0 -1
  9. nautobot/core/forms/__init__.py +2 -0
  10. nautobot/core/forms/forms.py +2 -1
  11. nautobot/core/forms/widgets.py +8 -0
  12. nautobot/core/management/commands/generate_performance_test_endpoints.py +268 -0
  13. nautobot/core/templates/generic/object_bulk_delete.html +1 -1
  14. nautobot/core/templates/generic/object_bulk_destroy.html +1 -1
  15. nautobot/core/templates/generic/object_bulk_edit.html +1 -1
  16. nautobot/core/templates/generic/object_bulk_import.html +1 -1
  17. nautobot/core/templates/generic/object_create.html +5 -0
  18. nautobot/core/templates/generic/object_delete.html +1 -1
  19. nautobot/core/templates/generic/object_detail.html +1 -1
  20. nautobot/core/templates/generic/object_edit.html +1 -1
  21. nautobot/core/templates/inc/javascript.html +2 -0
  22. nautobot/core/templates/widgets/clearable_file.html +5 -0
  23. nautobot/core/templatetags/helpers.py +3 -3
  24. nautobot/core/testing/integration.py +469 -12
  25. nautobot/core/tests/test_commands.py +31 -0
  26. nautobot/core/tests/test_jobs.py +34 -2
  27. nautobot/core/tests/test_utils.py +17 -2
  28. nautobot/core/utils/git.py +7 -2
  29. nautobot/core/utils/lookup.py +12 -1
  30. nautobot/core/views/generic.py +10 -2
  31. nautobot/core/views/mixins.py +22 -7
  32. nautobot/core/views/utils.py +2 -2
  33. nautobot/dcim/api/views.py +11 -10
  34. nautobot/dcim/forms.py +15 -6
  35. nautobot/dcim/models/devices.py +1 -2
  36. nautobot/dcim/tables/devices.py +2 -1
  37. nautobot/dcim/templates/dcim/cable.html +1 -1
  38. nautobot/dcim/templates/dcim/cable_trace.html +4 -4
  39. nautobot/dcim/templates/dcim/consoleport.html +14 -4
  40. nautobot/dcim/templates/dcim/consoleserverport.html +14 -4
  41. nautobot/dcim/templates/dcim/device/base.html +1 -1
  42. nautobot/dcim/templates/dcim/device/lldp_neighbors.html +3 -3
  43. nautobot/dcim/templates/dcim/device.html +2 -2
  44. nautobot/dcim/templates/dcim/device_component.html +1 -1
  45. nautobot/dcim/templates/dcim/devicetype.html +1 -1
  46. nautobot/dcim/templates/dcim/frontport.html +7 -2
  47. nautobot/dcim/templates/dcim/interface.html +9 -4
  48. nautobot/dcim/templates/dcim/location.html +1 -1
  49. nautobot/dcim/templates/dcim/locationtype.html +1 -1
  50. nautobot/dcim/templates/dcim/locationtype_retrieve.html +1 -1
  51. nautobot/dcim/templates/dcim/manufacturer.html +1 -1
  52. nautobot/dcim/templates/dcim/platform.html +1 -1
  53. nautobot/dcim/templates/dcim/powerfeed.html +9 -4
  54. nautobot/dcim/templates/dcim/poweroutlet.html +14 -4
  55. nautobot/dcim/templates/dcim/powerpanel.html +1 -1
  56. nautobot/dcim/templates/dcim/powerport.html +14 -4
  57. nautobot/dcim/templates/dcim/rack.html +1 -1
  58. nautobot/dcim/templates/dcim/rackgroup.html +1 -1
  59. nautobot/dcim/templates/dcim/rackreservation.html +2 -2
  60. nautobot/dcim/templates/dcim/rearport.html +7 -2
  61. nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
  62. nautobot/dcim/tests/integration/test_device_bulk_operations.py +30 -0
  63. nautobot/dcim/tests/integration/test_fileinputpicker.py +87 -0
  64. nautobot/dcim/tests/integration/test_location_bulk_operations.py +43 -0
  65. nautobot/dcim/tests/test_models.py +1 -1
  66. nautobot/dcim/tests/test_views.py +9 -1
  67. nautobot/dcim/views.py +12 -15
  68. nautobot/extras/api/serializers.py +33 -0
  69. nautobot/extras/api/views.py +13 -5
  70. nautobot/extras/constants.py +1 -0
  71. nautobot/extras/datasources/git.py +125 -0
  72. nautobot/extras/forms/forms.py +4 -0
  73. nautobot/extras/jobs.py +8 -1
  74. nautobot/extras/migrations/0122_add_graphqlquery_owner_content_type.py +34 -0
  75. nautobot/extras/models/customfields.py +29 -12
  76. nautobot/extras/models/datasources.py +85 -0
  77. nautobot/extras/models/models.py +15 -0
  78. nautobot/extras/models/relationships.py +17 -5
  79. nautobot/extras/signals.py +15 -1
  80. nautobot/extras/templates/extras/computedfield.html +1 -1
  81. nautobot/extras/templates/extras/configcontext.html +1 -1
  82. nautobot/extras/templates/extras/configcontextschema.html +1 -1
  83. nautobot/extras/templates/extras/customfield.html +1 -1
  84. nautobot/extras/templates/extras/customlink.html +1 -1
  85. nautobot/extras/templates/extras/dynamicgroup.html +1 -1
  86. nautobot/extras/templates/extras/exporttemplate.html +1 -1
  87. nautobot/extras/templates/extras/gitrepository.html +1 -1
  88. nautobot/extras/templates/extras/graphqlquery.html +1 -1
  89. nautobot/extras/templates/extras/job.html +1 -0
  90. nautobot/extras/templates/extras/job_detail.html +1 -1
  91. nautobot/extras/templates/extras/jobbutton_retrieve.html +1 -1
  92. nautobot/extras/templates/extras/jobhook.html +1 -1
  93. nautobot/extras/templates/extras/jobresult.html +1 -1
  94. nautobot/extras/templates/extras/objectchange.html +1 -1
  95. nautobot/extras/templates/extras/plugin_detail.html +1 -1
  96. nautobot/extras/templates/extras/relationship.html +1 -63
  97. nautobot/extras/templates/extras/role_retrieve.html +1 -1
  98. nautobot/extras/templates/extras/scheduledjob.html +1 -1
  99. nautobot/extras/templates/extras/secret.html +1 -1
  100. nautobot/extras/templates/extras/secretsgroup.html +1 -1
  101. nautobot/extras/templates/extras/status.html +1 -1
  102. nautobot/extras/templates/extras/tag.html +1 -1
  103. nautobot/extras/templates/extras/webhook.html +1 -1
  104. nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_interfaces.gql +8 -0
  105. nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_names.gql +5 -0
  106. nautobot/extras/tests/git_data/02-invalid-files/graphql_queries/bad_device_names.gql +5 -0
  107. nautobot/extras/tests/git_helper.py +9 -1
  108. nautobot/extras/tests/integration/__init__.py +29 -16
  109. nautobot/extras/tests/test_api.py +6 -0
  110. nautobot/extras/tests/test_customfields.py +49 -51
  111. nautobot/extras/tests/test_datasources.py +27 -0
  112. nautobot/extras/tests/test_dynamicgroups.py +14 -0
  113. nautobot/extras/tests/test_models.py +283 -0
  114. nautobot/extras/tests/test_utils.py +22 -1
  115. nautobot/extras/tests/test_views.py +197 -9
  116. nautobot/extras/utils.py +47 -8
  117. nautobot/extras/views.py +84 -26
  118. nautobot/ipam/api/views.py +3 -3
  119. nautobot/ipam/forms.py +2 -6
  120. nautobot/ipam/models.py +8 -2
  121. nautobot/ipam/tables.py +2 -2
  122. nautobot/ipam/templates/ipam/ipaddress.html +1 -1
  123. nautobot/ipam/templates/ipam/prefix.html +1 -1
  124. nautobot/ipam/templates/ipam/rir.html +1 -1
  125. nautobot/ipam/templates/ipam/routetarget.html +1 -1
  126. nautobot/ipam/templates/ipam/service.html +1 -1
  127. nautobot/ipam/templates/ipam/vlan.html +1 -1
  128. nautobot/ipam/templates/ipam/vlangroup.html +1 -1
  129. nautobot/ipam/templates/ipam/vrf.html +1 -1
  130. nautobot/ipam/tests/test_models.py +24 -0
  131. nautobot/ipam/tests/test_utils.py +41 -2
  132. nautobot/ipam/utils/__init__.py +18 -11
  133. nautobot/project-static/bootstrap-filestyle-1.2.3/bootstrap-filestyle.min.js +11 -0
  134. nautobot/project-static/docs/404.html +87 -12
  135. nautobot/project-static/docs/apps/index.html +88 -13
  136. nautobot/project-static/docs/apps/nautobot-apps.html +88 -13
  137. nautobot/project-static/docs/assets/javascripts/{bundle.88dd0f4e.min.js → bundle.60a45f97.min.js} +1 -1
  138. nautobot/project-static/docs/assets/javascripts/{bundle.88dd0f4e.min.js.map → bundle.60a45f97.min.js.map} +1 -1
  139. nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js → search.f8cc74c7.min.js} +1 -1
  140. nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js.map → search.f8cc74c7.min.js.map} +1 -1
  141. nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css → main.a40c8224.min.css} +1 -1
  142. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +87 -12
  143. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +87 -12
  144. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +87 -12
  145. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +87 -12
  146. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +87 -12
  147. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +87 -12
  148. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +87 -12
  149. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +87 -12
  150. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +87 -12
  151. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +87 -12
  152. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +87 -12
  153. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +87 -12
  154. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +87 -12
  155. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +87 -12
  156. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +87 -12
  157. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +87 -12
  158. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +87 -12
  159. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +87 -12
  160. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +87 -12
  161. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +87 -12
  162. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +87 -12
  163. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +87 -12
  164. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +177 -20
  165. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +114 -17
  166. nautobot/project-static/docs/development/apps/api/configuration-view.html +87 -12
  167. nautobot/project-static/docs/development/apps/api/database-backend-config.html +87 -12
  168. nautobot/project-static/docs/development/apps/api/models/django-admin.html +87 -12
  169. nautobot/project-static/docs/development/apps/api/models/global-search.html +87 -12
  170. nautobot/project-static/docs/development/apps/api/models/graphql.html +96 -21
  171. nautobot/project-static/docs/development/apps/api/models/index.html +87 -12
  172. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +87 -12
  173. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +87 -12
  174. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +89 -14
  175. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +87 -12
  176. nautobot/project-static/docs/development/apps/api/platform-features/index.html +87 -12
  177. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +87 -12
  178. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +87 -12
  179. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +87 -12
  180. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +87 -12
  181. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +87 -12
  182. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +87 -12
  183. nautobot/project-static/docs/development/apps/api/prometheus.html +87 -12
  184. nautobot/project-static/docs/development/apps/api/setup.html +88 -13
  185. nautobot/project-static/docs/development/apps/api/testing.html +87 -12
  186. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +87 -12
  187. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +87 -12
  188. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +87 -12
  189. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +87 -12
  190. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +87 -12
  191. nautobot/project-static/docs/development/apps/api/views/base-template.html +87 -12
  192. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +87 -12
  193. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +87 -12
  194. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +87 -12
  195. nautobot/project-static/docs/development/apps/api/views/index.html +87 -12
  196. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +87 -12
  197. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +87 -12
  198. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +87 -12
  199. nautobot/project-static/docs/development/apps/api/views/notes.html +87 -12
  200. nautobot/project-static/docs/development/apps/api/views/rest-api.html +87 -12
  201. nautobot/project-static/docs/development/apps/api/views/urls.html +87 -12
  202. nautobot/project-static/docs/development/apps/index.html +87 -12
  203. nautobot/project-static/docs/development/apps/migration/code-updates.html +93 -17
  204. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +89 -14
  205. nautobot/project-static/docs/development/apps/migration/from-v1.html +90 -15
  206. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +87 -12
  207. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +87 -12
  208. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +87 -12
  209. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +87 -12
  210. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +87 -12
  211. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +87 -12
  212. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +88 -13
  213. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +87 -12
  214. nautobot/project-static/docs/development/apps/porting-from-netbox.html +87 -12
  215. nautobot/project-static/docs/development/core/application-registry.html +87 -12
  216. nautobot/project-static/docs/development/core/best-practices.html +88 -13
  217. nautobot/project-static/docs/development/core/bootstrap-ui.html +88 -13
  218. nautobot/project-static/docs/development/core/caching.html +87 -12
  219. nautobot/project-static/docs/development/core/controllers.html +87 -12
  220. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +94 -19
  221. nautobot/project-static/docs/development/core/generic-views.html +87 -12
  222. nautobot/project-static/docs/development/core/getting-started.html +89 -14
  223. nautobot/project-static/docs/development/core/homepage.html +87 -12
  224. nautobot/project-static/docs/development/core/index.html +88 -13
  225. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +90 -15
  226. nautobot/project-static/docs/development/core/model-checklist.html +88 -13
  227. nautobot/project-static/docs/development/core/model-features.html +87 -12
  228. nautobot/project-static/docs/development/core/natural-keys.html +87 -12
  229. nautobot/project-static/docs/development/core/navigation-menu.html +88 -13
  230. nautobot/project-static/docs/development/core/release-checklist.html +88 -13
  231. nautobot/project-static/docs/development/core/role-internals.html +87 -12
  232. nautobot/project-static/docs/development/core/settings.html +88 -13
  233. nautobot/project-static/docs/development/core/style-guide.html +91 -16
  234. nautobot/project-static/docs/development/core/templates.html +88 -13
  235. nautobot/project-static/docs/development/core/testing.html +87 -12
  236. nautobot/project-static/docs/development/core/ui-component-framework.html +87 -12
  237. nautobot/project-static/docs/development/core/user-preferences.html +87 -12
  238. nautobot/project-static/docs/development/index.html +87 -12
  239. nautobot/project-static/docs/development/jobs/index.html +95 -13
  240. nautobot/project-static/docs/development/jobs/migration/from-v1.html +90 -14
  241. nautobot/project-static/docs/index.html +90 -14
  242. nautobot/project-static/docs/objects.inv +0 -0
  243. nautobot/project-static/docs/overview/application_stack.html +89 -14
  244. nautobot/project-static/docs/overview/design_philosophy.html +87 -12
  245. nautobot/project-static/docs/release-notes/index.html +87 -12
  246. nautobot/project-static/docs/release-notes/version-1.0.html +89 -14
  247. nautobot/project-static/docs/release-notes/version-1.1.html +89 -14
  248. nautobot/project-static/docs/release-notes/version-1.2.html +90 -15
  249. nautobot/project-static/docs/release-notes/version-1.3.html +88 -13
  250. nautobot/project-static/docs/release-notes/version-1.4.html +104 -29
  251. nautobot/project-static/docs/release-notes/version-1.5.html +95 -20
  252. nautobot/project-static/docs/release-notes/version-1.6.html +91 -16
  253. nautobot/project-static/docs/release-notes/version-2.0.html +97 -22
  254. nautobot/project-static/docs/release-notes/version-2.1.html +94 -19
  255. nautobot/project-static/docs/release-notes/version-2.2.html +88 -13
  256. nautobot/project-static/docs/release-notes/version-2.3.html +91 -16
  257. nautobot/project-static/docs/release-notes/version-2.4.html +465 -12
  258. nautobot/project-static/docs/requirements.txt +1 -1
  259. nautobot/project-static/docs/search/search_index.json +1 -1
  260. nautobot/project-static/docs/sitemap.xml +296 -288
  261. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  262. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +90 -15
  263. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +87 -12
  264. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +91 -16
  265. nautobot/project-static/docs/user-guide/administration/configuration/index.html +87 -12
  266. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +88 -13
  267. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +90 -15
  268. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +87 -12
  269. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +95 -20
  270. nautobot/project-static/docs/user-guide/administration/guides/docker.html +90 -15
  271. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +88 -13
  272. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +87 -12
  273. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +91 -16
  274. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +87 -12
  275. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +102 -27
  276. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +89 -14
  277. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +87 -12
  278. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +88 -13
  279. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +87 -12
  280. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +87 -12
  281. nautobot/project-static/docs/user-guide/administration/installation/index.html +87 -12
  282. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +88 -13
  283. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +93 -18
  284. nautobot/project-static/docs/user-guide/administration/installation/services.html +88 -13
  285. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +87 -12
  286. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +87 -12
  287. nautobot/project-static/docs/user-guide/administration/security/index.html +9420 -0
  288. nautobot/project-static/docs/user-guide/administration/security/notices.html +9844 -0
  289. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +87 -12
  290. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +88 -13
  291. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +87 -12
  292. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +87 -12
  293. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +87 -12
  294. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +87 -12
  295. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +87 -12
  296. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +87 -12
  297. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +87 -12
  298. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +98 -20
  299. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +87 -12
  300. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +87 -12
  301. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +87 -12
  302. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +87 -12
  303. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +87 -12
  304. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +87 -12
  305. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +87 -12
  306. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +87 -12
  307. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +87 -12
  308. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +87 -12
  309. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +87 -12
  310. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +87 -12
  311. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +87 -12
  312. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +87 -12
  313. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +87 -12
  314. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +87 -12
  315. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +87 -12
  316. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +87 -12
  317. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +87 -12
  318. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +87 -12
  319. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +87 -12
  320. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +87 -12
  321. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +87 -12
  322. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +87 -12
  323. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +87 -12
  324. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +87 -12
  325. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +87 -12
  326. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +87 -12
  327. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +87 -12
  328. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +87 -12
  329. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +87 -12
  330. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +87 -12
  331. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +87 -12
  332. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +87 -12
  333. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +87 -12
  334. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +87 -12
  335. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +87 -12
  336. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +87 -12
  337. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +87 -12
  338. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +87 -12
  339. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +87 -12
  340. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +87 -12
  341. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +87 -12
  342. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +87 -12
  343. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +87 -12
  344. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +87 -12
  345. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +87 -12
  346. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +87 -12
  347. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +87 -12
  348. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +87 -12
  349. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +87 -12
  350. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +87 -12
  351. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +87 -12
  352. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +87 -12
  353. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +87 -12
  354. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +87 -12
  355. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +87 -12
  356. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +87 -12
  357. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +87 -12
  358. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +87 -12
  359. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +87 -12
  360. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +87 -12
  361. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +87 -12
  362. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +87 -12
  363. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +87 -12
  364. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +87 -12
  365. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +87 -12
  366. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +87 -12
  367. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +87 -12
  368. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +87 -12
  369. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +87 -12
  370. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +87 -12
  371. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +87 -12
  372. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +87 -12
  373. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +87 -12
  374. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +87 -12
  375. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +87 -12
  376. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +87 -12
  377. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +87 -12
  378. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +87 -12
  379. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +87 -12
  380. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +99 -24
  381. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +87 -12
  382. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +87 -12
  383. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +90 -15
  384. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +87 -12
  385. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +87 -12
  386. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +87 -12
  387. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +87 -12
  388. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +87 -12
  389. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +87 -12
  390. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +188 -30
  391. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +87 -12
  392. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +87 -12
  393. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +87 -12
  394. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +87 -12
  395. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +87 -12
  396. nautobot/project-static/docs/user-guide/index.html +87 -12
  397. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +87 -12
  398. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +87 -12
  399. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +87 -12
  400. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +87 -12
  401. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +88 -13
  402. nautobot/project-static/docs/user-guide/platform-functionality/events.html +87 -12
  403. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +87 -12
  404. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +87 -12
  405. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +407 -14
  406. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +90 -15
  407. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +87 -12
  408. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +87 -12
  409. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +87 -12
  410. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +87 -12
  411. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +87 -12
  412. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +87 -12
  413. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +89 -14
  414. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +93 -18
  415. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +87 -12
  416. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +87 -12
  417. nautobot/project-static/docs/user-guide/platform-functionality/note.html +87 -12
  418. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +87 -12
  419. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +87 -12
  420. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +87 -12
  421. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +87 -12
  422. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +87 -12
  423. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +87 -12
  424. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +87 -12
  425. nautobot/project-static/docs/user-guide/platform-functionality/role.html +87 -12
  426. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +87 -12
  427. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +87 -12
  428. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +87 -12
  429. nautobot/project-static/docs/user-guide/platform-functionality/status.html +87 -12
  430. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +87 -12
  431. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +87 -12
  432. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +87 -12
  433. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +87 -12
  434. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +87 -12
  435. nautobot/project-static/js/dropdown.js +28 -0
  436. nautobot/tenancy/forms.py +9 -0
  437. nautobot/tenancy/templates/tenancy/tenant.html +1 -2
  438. nautobot/tenancy/templates/tenancy/tenant_create.html +21 -0
  439. nautobot/tenancy/templates/tenancy/tenant_edit.html +2 -21
  440. nautobot/tenancy/templates/tenancy/tenantgroup.html +2 -44
  441. nautobot/tenancy/templates/tenancy/tenantgroup_retrieve.html +1 -0
  442. nautobot/tenancy/tests/test_views.py +5 -1
  443. nautobot/tenancy/urls.py +7 -79
  444. nautobot/tenancy/views.py +51 -80
  445. nautobot/virtualization/templates/virtualization/cluster.html +1 -1
  446. nautobot/virtualization/templates/virtualization/clustergroup.html +1 -1
  447. nautobot/virtualization/templates/virtualization/clustertype.html +1 -1
  448. nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
  449. nautobot/virtualization/templates/virtualization/vminterface.html +1 -1
  450. nautobot/wireless/api/serializers.py +6 -1
  451. nautobot/wireless/api/views.py +3 -3
  452. nautobot/wireless/tests/test_api.py +5 -0
  453. {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/METADATA +12 -12
  454. {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/RECORD +459 -443
  455. nautobot/dcim/tests/integration/test_device_bulk_delete.py +0 -189
  456. nautobot/dcim/tests/integration/test_device_bulk_edit.py +0 -181
  457. /nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css.map → main.a40c8224.min.css.map} +0 -0
  458. {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/LICENSE.txt +0 -0
  459. {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/NOTICE +0 -0
  460. {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/WHEEL +0 -0
  461. {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/entry_points.txt +0 -0
nautobot/extras/utils.py CHANGED
@@ -6,6 +6,7 @@ import hmac
6
6
  import logging
7
7
  import re
8
8
  import sys
9
+ from typing import Optional
9
10
 
10
11
  from django.apps import apps
11
12
  from django.conf import settings
@@ -13,7 +14,7 @@ from django.contrib.contenttypes.models import ContentType
13
14
  from django.core.cache import cache
14
15
  from django.core.validators import ValidationError
15
16
  from django.db import transaction
16
- from django.db.models import Q
17
+ from django.db.models import Model, Q
17
18
  from django.template.loader import get_template, TemplateDoesNotExist
18
19
  from django.utils.deconstruct import deconstructible
19
20
  import kubernetes.client
@@ -21,9 +22,12 @@ import redis.exceptions
21
22
 
22
23
  from nautobot.core.choices import ColorChoices
23
24
  from nautobot.core.constants import CHARFIELD_MAX_LENGTH
25
+ from nautobot.core.exceptions import FilterSetFieldNotFound
24
26
  from nautobot.core.models.managers import TagsManager
25
27
  from nautobot.core.models.utils import find_models_with_matching_fields
26
28
  from nautobot.core.utils.data import is_uuid
29
+ from nautobot.core.utils.lookup import get_filterset_for_model, get_model_for_view_name
30
+ from nautobot.core.utils.requests import is_single_choice_field
27
31
  from nautobot.extras.choices import DynamicGroupTypeChoices, JobQueueTypeChoices, ObjectChangeActionChoices
28
32
  from nautobot.extras.constants import (
29
33
  CHANGELOG_MAX_CHANGE_CONTEXT_DETAIL,
@@ -36,16 +40,24 @@ from nautobot.extras.registry import registry
36
40
  logger = logging.getLogger(__name__)
37
41
 
38
42
 
39
- def get_base_template(base_template, model):
43
+ def get_base_template(base_template: Optional[str], model: type[Model]) -> str:
40
44
  """
41
- Returns the name of the base template, if the base_template is not None
42
- Otherwise, default to using "<app>/<model>.html" as the base template, if it exists.
43
- Otherwise, check if "<app>/<model>_retrieve.html" used in `NautobotUIViewSet` exists.
44
- If both templates do not exist, fall back to "base.html".
45
+ Attempt to locate the correct base template for an object detail view and related views, if one was not specified.
46
+
47
+ Args:
48
+ base_template (str, optional): If not None, this explicitly specified template will be preferred.
49
+ model (Model): The model to identify a base template for, if base_template is None.
50
+
51
+ Returns the specified `base_template`, if not `None`.
52
+ Otherwise, if `"<app>/<model_name>.html"` exists (legacy ObjectView pattern), returns that string.
53
+ Otherwise, if `"<app>/<model_name>_retrieve.html"` exists (as used in `NautobotUIViewSet`), returns that string.
54
+ If all else fails, returns `"generic/object_retrieve.html"`.
55
+
56
+ Note: before Nautobot 2.4.2, this API would default to "base.html" rather than "generic/object_retrieve.html".
57
+ This behavior was changed to the current behavior to address issue #6550 and similar incorrect behavior.
45
58
  """
46
59
  if base_template is None:
47
60
  base_template = f"{model._meta.app_label}/{model._meta.model_name}.html"
48
- # 2.0 TODO(Hanlin): This can be removed once an object view has been established for every model.
49
61
  try:
50
62
  get_template(base_template)
51
63
  except TemplateDoesNotExist:
@@ -53,7 +65,7 @@ def get_base_template(base_template, model):
53
65
  try:
54
66
  get_template(base_template)
55
67
  except TemplateDoesNotExist:
56
- base_template = "base.html"
68
+ base_template = "generic/object_retrieve.html"
57
69
  return base_template
58
70
 
59
71
 
@@ -871,3 +883,30 @@ def bulk_delete_with_bulk_change_logging(qs, batch_size=1000):
871
883
  finally:
872
884
  change_context.defer_object_changes = False
873
885
  change_context.reset_deferred_object_changes()
886
+
887
+
888
+ def fixup_filterset_query_params(param_dict, view_name, non_filter_params):
889
+ """
890
+ Called before saving query filter parameters to a SavedView's config. This function will format
891
+ single value query parameters to be saved as a single values instead of lists of singles values.
892
+
893
+ Args:
894
+ param_dict (dict): key-value pairs of query parameters.
895
+ view_name (str): The name of the view that the saved view is associated with. "dcim:location_list" for example.
896
+ non_filter_params (list): List of non-query parameters that should not be formatted.
897
+ """
898
+ model = get_model_for_view_name(view_name)
899
+ try:
900
+ filterset_class = get_filterset_for_model(model)
901
+ except TypeError:
902
+ return param_dict
903
+
904
+ filterset = filterset_class()
905
+
906
+ for filter_field, value in param_dict.items():
907
+ try:
908
+ if filter_field not in non_filter_params and is_single_choice_field(filterset, filter_field):
909
+ param_dict[filter_field] = value[0]
910
+ except FilterSetFieldNotFound:
911
+ pass
912
+ return param_dict
nautobot/extras/views.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ from typing import Optional
2
3
  from urllib.parse import parse_qs
3
4
 
4
5
  from django.contrib import messages
@@ -25,21 +26,24 @@ from rest_framework.permissions import IsAuthenticated
25
26
 
26
27
  from nautobot.core.constants import PAGINATE_COUNT_DEFAULT
27
28
  from nautobot.core.events import publish_event
29
+ from nautobot.core.exceptions import FilterSetFieldNotFound
28
30
  from nautobot.core.forms import restrict_form_fields
29
31
  from nautobot.core.models.querysets import count_related
30
32
  from nautobot.core.models.utils import pretty_print_query, serialize_object_v2
31
33
  from nautobot.core.tables import ButtonsColumn
32
34
  from nautobot.core.ui import object_detail
33
35
  from nautobot.core.ui.choices import SectionChoices
36
+ from nautobot.core.ui.object_detail import ObjectDetailContent, ObjectFieldsPanel
34
37
  from nautobot.core.utils.config import get_settings_or_config
35
38
  from nautobot.core.utils.lookup import (
36
39
  get_filterset_for_model,
40
+ get_model_for_view_name,
37
41
  get_route_for_model,
38
42
  get_table_class_string_from_view_name,
39
43
  get_table_for_model,
40
44
  )
41
45
  from nautobot.core.utils.permissions import get_permission_for_model
42
- from nautobot.core.utils.requests import normalize_querydict
46
+ from nautobot.core.utils.requests import is_single_choice_field, normalize_querydict
43
47
  from nautobot.core.views import generic, viewsets
44
48
  from nautobot.core.views.mixins import (
45
49
  GetReturnURLMixin,
@@ -67,7 +71,7 @@ from nautobot.dcim.tables import (
67
71
  VirtualDeviceContextTable,
68
72
  )
69
73
  from nautobot.extras.context_managers import deferred_change_logging_for_bulk_operation
70
- from nautobot.extras.utils import get_base_template, get_job_queue, get_worker_count
74
+ from nautobot.extras.utils import fixup_filterset_query_params, get_base_template, get_job_queue, get_worker_count
71
75
  from nautobot.ipam.models import IPAddress, Prefix, VLAN
72
76
  from nautobot.ipam.tables import IPAddressTable, PrefixTable, VLANTable
73
77
  from nautobot.virtualization.models import VirtualMachine, VMInterface
@@ -718,8 +722,13 @@ class DynamicGroupView(generic.ObjectView):
718
722
 
719
723
  if table_class is not None:
720
724
  # Members table (for display on Members nav tab)
725
+ if hasattr(members, "without_tree_fields"):
726
+ members = members.without_tree_fields()
721
727
  members_table = table_class(
722
- members.restrict(request.user, "view"), orderable=False, exclude=["dynamic_group_count"]
728
+ members.restrict(request.user, "view"),
729
+ orderable=False,
730
+ exclude=["dynamic_group_count"],
731
+ hide_hierarchy_ui=True,
723
732
  )
724
733
  paginate = {
725
734
  "paginator_class": EnhancedPaginator,
@@ -900,10 +909,13 @@ class DynamicGroupBulkDeleteView(generic.BulkDeleteView):
900
909
  class ObjectDynamicGroupsView(generic.GenericView):
901
910
  """
902
911
  Present a list of dynamic groups associated to a particular object.
903
- base_template: The name of the template to extend. If not provided, "<app>/<model>.html" will be used.
912
+
913
+ base_template: Specify to explicitly identify the base object detail template to render.
914
+ If not provided, "<app>/<model>.html", "<app>/<model>_retrieve.html", or "generic/object_retrieve.html"
915
+ will be used, as per `get_base_template()`.
904
916
  """
905
917
 
906
- base_template = None
918
+ base_template: Optional[str] = None
907
919
 
908
920
  def get(self, request, model, **kwargs):
909
921
  # Handle QuerySet restriction of parent object if needed
@@ -926,7 +938,7 @@ class ObjectDynamicGroupsView(generic.GenericView):
926
938
  }
927
939
  RequestConfig(request, paginate).configure(dynamicgroups_table)
928
940
 
929
- self.base_template = get_base_template(self.base_template, model)
941
+ base_template = get_base_template(self.base_template, model)
930
942
 
931
943
  return render(
932
944
  request,
@@ -936,7 +948,7 @@ class ObjectDynamicGroupsView(generic.GenericView):
936
948
  "verbose_name": obj._meta.verbose_name,
937
949
  "verbose_name_plural": obj._meta.verbose_name_plural,
938
950
  "table": dynamicgroups_table,
939
- "base_template": self.base_template,
951
+ "base_template": base_template,
940
952
  "active_tab": "dynamic-groups",
941
953
  },
942
954
  )
@@ -1260,9 +1272,10 @@ class JobListView(generic.ObjectListView):
1260
1272
  def alter_queryset(self, request):
1261
1273
  queryset = super().alter_queryset(request)
1262
1274
  # Default to hiding "hidden" and non-installed jobs
1263
- if "hidden" not in request.GET:
1275
+ filter_params = self.get_filter_params(request)
1276
+ if "hidden" not in filter_params:
1264
1277
  queryset = queryset.filter(hidden=False)
1265
- if "installed" not in request.GET:
1278
+ if "installed" not in filter_params:
1266
1279
  queryset = queryset.filter(installed=True)
1267
1280
  return queryset
1268
1281
 
@@ -1801,15 +1814,23 @@ class SavedViewUIViewSet(
1801
1814
  if sort_order:
1802
1815
  sv.config["sort_order"] = sort_order
1803
1816
 
1817
+ model = get_model_for_view_name(sv.view)
1818
+ filterset_class = get_filterset_for_model(model)
1819
+ filterset = filterset_class()
1804
1820
  filter_params = {}
1805
1821
  for key in request.GET:
1806
1822
  if key in self.non_filter_params:
1807
1823
  continue
1808
- # TODO: this is fragile, other single-value filters will also be unhappy if given a list
1809
- if key == "q":
1810
- filter_params[key] = request.GET.get(key)
1811
- else:
1812
- filter_params[key] = request.GET.getlist(key)
1824
+ try:
1825
+ if is_single_choice_field(filterset, key):
1826
+ filter_params[key] = request.GET.getlist(key)[0]
1827
+ except FilterSetFieldNotFound:
1828
+ continue
1829
+ try:
1830
+ if not is_single_choice_field(filterset, key):
1831
+ filter_params[key] = request.GET.getlist(key)
1832
+ except FilterSetFieldNotFound:
1833
+ continue
1813
1834
 
1814
1835
  if filter_params:
1815
1836
  sv.config["filter_params"] = filter_params
@@ -1834,14 +1855,14 @@ class SavedViewUIViewSet(
1834
1855
  and the name of the new SavedView from request.POST to create a new SavedView.
1835
1856
  """
1836
1857
  name = request.POST.get("name")
1858
+ view_name = request.POST.get("view")
1837
1859
  is_shared = request.POST.get("is_shared", False)
1838
1860
  if is_shared:
1839
1861
  is_shared = True
1840
1862
  params = request.POST.get("params", "")
1863
+ param_dict = fixup_filterset_query_params(parse_qs(params), view_name, self.non_filter_params)
1841
1864
 
1842
- param_dict = parse_qs(params)
1843
-
1844
- single_value_params = ["saved_view", "table_changes_pending", "all_filters_removed", "q", "per_page"]
1865
+ single_value_params = ["saved_view", "table_changes_pending", "all_filters_removed", "per_page"]
1845
1866
  for key in param_dict.keys():
1846
1867
  if key in single_value_params:
1847
1868
  param_dict[key] = param_dict[key][0]
@@ -1850,7 +1871,6 @@ class SavedViewUIViewSet(
1850
1871
  derived_instance = None
1851
1872
  if derived_view_pk:
1852
1873
  derived_instance = self.get_queryset().get(pk=derived_view_pk)
1853
- view_name = request.POST.get("view")
1854
1874
  try:
1855
1875
  reverse(view_name)
1856
1876
  except NoReverseMatch:
@@ -2185,10 +2205,13 @@ class ObjectChangeView(generic.ObjectView):
2185
2205
  class ObjectChangeLogView(generic.GenericView):
2186
2206
  """
2187
2207
  Present a history of changes made to a particular object.
2188
- base_template: The name of the template to extend. If not provided, "<app>/<model>.html" will be used.
2208
+
2209
+ base_template: Specify to explicitly identify the base object detail template to render.
2210
+ If not provided, "<app>/<model>.html", "<app>/<model>_retrieve.html", or "generic/object_retrieve.html"
2211
+ will be used, as per `get_base_template()`.
2189
2212
  """
2190
2213
 
2191
- base_template = None
2214
+ base_template: Optional[str] = None
2192
2215
 
2193
2216
  def get(self, request, model, **kwargs):
2194
2217
  # Handle QuerySet restriction of parent object if needed
@@ -2216,7 +2239,7 @@ class ObjectChangeLogView(generic.GenericView):
2216
2239
  }
2217
2240
  RequestConfig(request, paginate).configure(objectchanges_table)
2218
2241
 
2219
- self.base_template = get_base_template(self.base_template, model)
2242
+ base_template = get_base_template(self.base_template, model)
2220
2243
 
2221
2244
  return render(
2222
2245
  request,
@@ -2226,7 +2249,7 @@ class ObjectChangeLogView(generic.GenericView):
2226
2249
  "verbose_name": obj._meta.verbose_name,
2227
2250
  "verbose_name_plural": obj._meta.verbose_name_plural,
2228
2251
  "table": objectchanges_table,
2229
- "base_template": self.base_template,
2252
+ "base_template": base_template,
2230
2253
  "active_tab": "changelog",
2231
2254
  },
2232
2255
  )
@@ -2319,10 +2342,13 @@ class NoteDeleteView(generic.ObjectDeleteView):
2319
2342
  class ObjectNotesView(generic.GenericView):
2320
2343
  """
2321
2344
  Present a list of notes associated to a particular object.
2322
- base_template: The name of the template to extend. If not provided, "<app>/<model>.html" will be used.
2345
+
2346
+ base_template: Specify to explicitly identify the base object detail template to render.
2347
+ If not provided, "<app>/<model>.html", "<app>/<model>_retrieve.html", or "generic/object_retrieve.html"
2348
+ will be used, as per `get_base_template()`.
2323
2349
  """
2324
2350
 
2325
- base_template = None
2351
+ base_template: Optional[str] = None
2326
2352
 
2327
2353
  def get(self, request, model, **kwargs):
2328
2354
  # Handle QuerySet restriction of parent object if needed
@@ -2346,7 +2372,7 @@ class ObjectNotesView(generic.GenericView):
2346
2372
  }
2347
2373
  RequestConfig(request, paginate).configure(notes_table)
2348
2374
 
2349
- self.base_template = get_base_template(self.base_template, model)
2375
+ base_template = get_base_template(self.base_template, model)
2350
2376
 
2351
2377
  return render(
2352
2378
  request,
@@ -2356,7 +2382,7 @@ class ObjectNotesView(generic.GenericView):
2356
2382
  "verbose_name": obj._meta.verbose_name,
2357
2383
  "verbose_name_plural": obj._meta.verbose_name_plural,
2358
2384
  "table": notes_table,
2359
- "base_template": self.base_template,
2385
+ "base_template": base_template,
2360
2386
  "active_tab": "notes",
2361
2387
  "form": notes_form,
2362
2388
  },
@@ -2378,6 +2404,38 @@ class RelationshipListView(generic.ObjectListView):
2378
2404
 
2379
2405
  class RelationshipView(generic.ObjectView):
2380
2406
  queryset = Relationship.objects.all()
2407
+ object_detail_content = ObjectDetailContent(
2408
+ panels=(
2409
+ ObjectFieldsPanel(
2410
+ label="Relationship",
2411
+ section=SectionChoices.LEFT_HALF,
2412
+ weight=100,
2413
+ fields="__all__",
2414
+ exclude_fields=[
2415
+ "source_type",
2416
+ "source_label",
2417
+ "source_hidden",
2418
+ "source_filter",
2419
+ "destination_type",
2420
+ "destination_label",
2421
+ "destination_hidden",
2422
+ "destination_filter",
2423
+ ],
2424
+ ),
2425
+ ObjectFieldsPanel(
2426
+ label="Source Attributes",
2427
+ section=SectionChoices.RIGHT_HALF,
2428
+ weight=100,
2429
+ fields=["source_type", "source_label", "source_hidden", "source_filter"],
2430
+ ),
2431
+ ObjectFieldsPanel(
2432
+ label="Destination Attributes",
2433
+ section=SectionChoices.RIGHT_HALF,
2434
+ weight=200,
2435
+ fields=["destination_type", "destination_label", "destination_hidden", "destination_filter"],
2436
+ ),
2437
+ )
2438
+ )
2381
2439
 
2382
2440
 
2383
2441
  class RelationshipEditView(generic.ObjectEditView):
@@ -13,7 +13,7 @@ from nautobot.core.constants import MAX_PAGE_SIZE_DEFAULT, PAGINATE_COUNT_DEFAUL
13
13
  from nautobot.core.models.querysets import count_related
14
14
  from nautobot.core.utils.config import get_settings_or_config
15
15
  from nautobot.dcim.models import Location
16
- from nautobot.extras.api.views import NautobotModelViewSet
16
+ from nautobot.extras.api.views import ModelViewSet, NautobotModelViewSet
17
17
  from nautobot.ipam import filters
18
18
  from nautobot.ipam.api import serializers
19
19
  from nautobot.ipam.models import (
@@ -323,7 +323,7 @@ class PrefixViewSet(NautobotModelViewSet):
323
323
  return Response(serializer.data)
324
324
 
325
325
 
326
- class PrefixLocationAssignmentViewSet(NautobotModelViewSet):
326
+ class PrefixLocationAssignmentViewSet(ModelViewSet):
327
327
  queryset = PrefixLocationAssignment.objects.all()
328
328
  serializer_class = serializers.PrefixLocationAssignmentSerializer
329
329
  filterset_class = filters.PrefixLocationAssignmentFilterSet
@@ -581,7 +581,7 @@ class VLANViewSet(NautobotModelViewSet):
581
581
  raise self.LocationIncompatibleLegacyBehavior from e
582
582
 
583
583
 
584
- class VLANLocationAssignmentViewSet(NautobotModelViewSet):
584
+ class VLANLocationAssignmentViewSet(ModelViewSet):
585
585
  queryset = VLANLocationAssignment.objects.all()
586
586
  serializer_class = serializers.VLANLocationAssignmentSerializer
587
587
  filterset_class = filters.VLANLocationAssignmentFilterSet
nautobot/ipam/forms.py CHANGED
@@ -674,13 +674,9 @@ class IPAddressFilterForm(NautobotFilterForm, TenancyFilterForm, StatusModelFilt
674
674
  "has_nat_inside",
675
675
  ]
676
676
  q = forms.CharField(required=False, label="Search")
677
- parent = forms.CharField(
677
+ parent = DynamicModelMultipleChoiceField(
678
+ queryset=Prefix.objects.all(),
678
679
  required=False,
679
- widget=forms.TextInput(
680
- attrs={
681
- "placeholder": "Prefix",
682
- }
683
- ),
684
680
  label="Parent Prefix",
685
681
  )
686
682
  ip_version = forms.ChoiceField(
nautobot/ipam/models.py CHANGED
@@ -975,7 +975,10 @@ class Prefix(PrimaryModel):
975
975
  while `<Prefix 10.0.0.0/16>.get_all_ips()` will return *both* 10.0.0.1.24 and 10.0.1.1/24.
976
976
  """
977
977
  return IPAddress.objects.filter(
978
- parent__namespace=self.namespace, host__gte=self.network, host__lte=self.broadcast
978
+ parent__namespace=self.namespace,
979
+ ip_version=self.ip_version,
980
+ host__gte=self.network,
981
+ host__lte=self.broadcast,
979
982
  )
980
983
 
981
984
  def get_first_available_prefix(self):
@@ -1016,7 +1019,10 @@ class Prefix(PrimaryModel):
1016
1019
  # change this when that is the case, see #3873 for historical context.
1017
1020
  if self.type != choices.PrefixTypeChoices.TYPE_CONTAINER:
1018
1021
  pool_ips = IPAddress.objects.filter(
1019
- parent__namespace=self.namespace, host__gte=self.network, host__lte=self.broadcast
1022
+ parent__namespace=self.namespace,
1023
+ ip_version=self.ip_version,
1024
+ host__gte=self.network,
1025
+ host__lte=self.broadcast,
1020
1026
  ).values_list("host", flat=True)
1021
1027
  child_ips = netaddr.IPSet(pool_ips)
1022
1028
 
nautobot/ipam/tables.py CHANGED
@@ -79,7 +79,7 @@ IPADDRESS_LINK = """
79
79
  {% elif perms.ipam.add_ipaddress %}
80
80
  <a href="\
81
81
  {% url 'ipam:ipaddress_add' %}\
82
- ?address={{ record.1 }}\
82
+ ?address={{ record.1 }}&namespace={{ object.namespace.pk }}\
83
83
  {% if object.vrf %}&vrf={{ object.vrf.pk }}{% endif %}\
84
84
  {% if object.tenant %}&tenant={{ object.tenant.pk }}{% endif %}\
85
85
  " class="btn btn-xs btn-success">\
@@ -101,7 +101,7 @@ IPADDRESS_COPY_LINK = """
101
101
  {% elif perms.ipam.add_ipaddress %}
102
102
  <a href="\
103
103
  {% url 'ipam:ipaddress_add' %}\
104
- ?address={{ record.1 }}\
104
+ ?address={{ record.1 }}&namespace={{ object.namespace.pk }}\
105
105
  {% if object.vrf %}&vrf={{ object.vrf.pk }}{% endif %}\
106
106
  {% if object.tenant %}&tenant={{ object.tenant.pk }}{% endif %}\
107
107
  " class="btn btn-xs btn-success">\
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load helpers %}
3
3
  {% load render_table from django_tables2 %}
4
4
 
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load helpers %}
3
3
 
4
4
  {% block breadcrumbs %}
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load helpers %}
3
3
 
4
4
  {% block content_left_page %}
@@ -1 +1 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load helpers %}
3
3
 
4
4
  {% block extra_breadcrumbs %}
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load helpers %}
3
3
 
4
4
  {% block extra_breadcrumbs %}
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load buttons %}
3
3
  {% load helpers %}
4
4
  {% load plugins %}
@@ -1 +1 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
@@ -827,6 +827,24 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
827
827
 
828
828
  self.assertEqual(parent_prefix.get_first_available_ip(), "10.0.3.2/29")
829
829
 
830
+ def test_get_all_ips_issue_3319(self):
831
+ # https://github.com/nautobot/nautobot/issues/3319
832
+ # Confirm that IPv4 addresses aren't caught up in the IPv6 ::/96 subnet by accident, and vice versa.
833
+ prefix_v6 = Prefix.objects.create(
834
+ prefix="::/0", type=PrefixTypeChoices.TYPE_CONTAINER, status=self.status, namespace=self.namespace
835
+ )
836
+ prefix_v4 = Prefix.objects.create(
837
+ prefix="0.0.0.0/0", type=PrefixTypeChoices.TYPE_CONTAINER, status=self.status, namespace=self.namespace
838
+ )
839
+ IPAddress.objects.create(address="::0102:0304/128", status=self.status, namespace=self.namespace)
840
+ IPAddress.objects.create(address="1.2.3.4/32", status=self.status, namespace=self.namespace)
841
+ self.assertQuerysetEqualAndNotEmpty(
842
+ prefix_v6.get_all_ips(), IPAddress.objects.filter(ip_version=6, parent__namespace=self.namespace)
843
+ )
844
+ self.assertQuerysetEqualAndNotEmpty(
845
+ prefix_v4.get_all_ips(), IPAddress.objects.filter(ip_version=4, parent__namespace=self.namespace)
846
+ )
847
+
830
848
  def test_get_utilization(self):
831
849
  # Container Prefix
832
850
  prefix = Prefix.objects.create(
@@ -982,6 +1000,12 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
982
1000
  Prefix.objects.create(prefix="ab80::/9", status=self.status, namespace=self.namespace)
983
1001
  self.assertEqual(large_prefix_v6.get_utilization(), (2**120, 2**120))
984
1002
 
1003
+ # https://github.com/nautobot/nautobot/issues/3319
1004
+ v4_10dot_address_space_in_v6 = Prefix.objects.create(
1005
+ prefix="0a00::/8", type=PrefixTypeChoices.TYPE_NETWORK, status=self.status, namespace=self.namespace
1006
+ )
1007
+ self.assertSequenceEqual(v4_10dot_address_space_in_v6.get_utilization(), (0, 2**120))
1008
+
985
1009
  #
986
1010
  # Uniqueness enforcement tests
987
1011
  #
@@ -1,9 +1,10 @@
1
1
  from django.test import TestCase
2
+ import netaddr
2
3
 
3
4
  from nautobot.core.forms.utils import parse_numeric_range
4
5
  from nautobot.extras.models import Status
5
- from nautobot.ipam.models import VLAN, VLANGroup
6
- from nautobot.ipam.utils import add_available_vlans
6
+ from nautobot.ipam.models import IPAddress, Namespace, Prefix, VLAN, VLANGroup
7
+ from nautobot.ipam.utils import add_available_ipaddresses, add_available_vlans
7
8
 
8
9
 
9
10
  class AddAvailableVlansTest(TestCase):
@@ -27,6 +28,44 @@ class AddAvailableVlansTest(TestCase):
27
28
  )
28
29
 
29
30
 
31
+ class AddAvailableIPsTest(TestCase):
32
+ """Tests for add_available_ipaddresses()."""
33
+
34
+ def test_add_available_ipaddresses_ipv4(self):
35
+ prefix = Prefix.objects.create(prefix="22.22.22.0/24", status=Status.objects.get_for_model(Prefix).first())
36
+ ip_status = Status.objects.get_for_model(IPAddress).first()
37
+ # .0 isn't available since this isn't a Pool prefix
38
+ available_1 = (9, "22.22.22.1/24")
39
+ ip_1 = IPAddress.objects.create(address="22.22.22.10/24", status=ip_status)
40
+ available_2 = (10, "22.22.22.11/24")
41
+ ip_2 = IPAddress.objects.create(address="22.22.22.21/24", status=ip_status)
42
+ available_3 = (233, "22.22.22.22/24")
43
+ # .255 isn't available since this isn't a Pool prefix
44
+ self.assertEqual(
45
+ add_available_ipaddresses(prefix=netaddr.IPNetwork(prefix.prefix), ipaddress_list=(ip_1, ip_2)),
46
+ [available_1, ip_1, available_2, ip_2, available_3],
47
+ )
48
+
49
+ def test_add_available_ipaddresses_ipv6(self):
50
+ namespace = Namespace.objects.create(name="add_available_ipv6")
51
+ prefix = Prefix.objects.create(
52
+ prefix="::/0", status=Status.objects.get_for_model(Prefix).first(), namespace=namespace
53
+ )
54
+ ip_status = Status.objects.get_for_model(IPAddress).first()
55
+ # .0 is available in IPv6
56
+ available_1 = (10, "::/0")
57
+ ip_1 = IPAddress.objects.create(address="::a/0", status=ip_status, namespace=namespace)
58
+ available_2 = (2**128 - 10 - 10 - 2, "::b/0")
59
+ ip_2 = IPAddress.objects.create(
60
+ address="ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff5/0", status=ip_status, namespace=namespace
61
+ )
62
+ available_3 = (10, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff6/0")
63
+ self.assertEqual(
64
+ add_available_ipaddresses(prefix=netaddr.IPNetwork(prefix.prefix), ipaddress_list=(ip_1, ip_2)),
65
+ [available_1, ip_1, available_2, ip_2, available_3],
66
+ )
67
+
68
+
30
69
  class ParseNumericRangeTest(TestCase):
31
70
  """Tests for add_available_vlans()."""
32
71
 
@@ -41,20 +41,27 @@ def get_add_available_prefixes_callback(show_available: bool, parent: Prefix):
41
41
 
42
42
  def add_available_ipaddresses(prefix: netaddr.IPNetwork, ipaddress_list: Iterable[IPAddress], is_pool: bool = False):
43
43
  """
44
- Annotate ranges of available IP addresses within a given prefix. If is_pool is True, the first and last IP will be
45
- considered usable (regardless of mask length).
46
- """
44
+ Annotate ranges of available IP addresses within a given prefix.
45
+
46
+ Args:
47
+ prefix (netaddr.IPNetwork): The network to calculate available addresses within.
48
+ ipaddress_list (Iterable[IPAddress]): List or QuerySet of extant IPAddress objects.
49
+ is_pool (bool): If True, the first/last IPs in the prefix will be considered usable, regardless of mask length.
47
50
 
51
+ Returns:
52
+ The contents of `ipaddress_list` interleaved with tuples of the form
53
+ `(number_of_available_addresses, first_such_address)`.
54
+ """
48
55
  output = []
49
56
  prev_ip = None
50
57
 
51
58
  # Ignore the network and broadcast addresses for non-pool IPv4 prefixes larger than /31.
52
59
  if prefix.version == 4 and prefix.prefixlen < 31 and not is_pool:
53
- first_ip_in_prefix = netaddr.IPAddress(prefix.first + 1)
54
- last_ip_in_prefix = netaddr.IPAddress(prefix.last - 1)
60
+ first_ip_in_prefix = netaddr.IPAddress(prefix.first + 1, version=prefix.version)
61
+ last_ip_in_prefix = netaddr.IPAddress(prefix.last - 1, version=prefix.version)
55
62
  else:
56
- first_ip_in_prefix = netaddr.IPAddress(prefix.first)
57
- last_ip_in_prefix = netaddr.IPAddress(prefix.last)
63
+ first_ip_in_prefix = netaddr.IPAddress(prefix.first, version=prefix.version)
64
+ last_ip_in_prefix = netaddr.IPAddress(prefix.last, version=prefix.version)
58
65
 
59
66
  if not ipaddress_list:
60
67
  return [
@@ -71,15 +78,15 @@ def add_available_ipaddresses(prefix: netaddr.IPNetwork, ipaddress_list: Iterabl
71
78
  ipaddress_list.sort(key=lambda ip: ip.host)
72
79
 
73
80
  # Account for any available IPs before the first real IP
74
- if ipaddress_list[0].address.ip > first_ip_in_prefix:
75
- skipped_count = int(ipaddress_list[0].address.ip - first_ip_in_prefix)
81
+ if ipaddress_list[0].address.ip.value > first_ip_in_prefix.value:
82
+ skipped_count = ipaddress_list[0].address.ip.value - first_ip_in_prefix.value
76
83
  first_skipped = f"{first_ip_in_prefix}/{prefix.prefixlen}"
77
84
  output.append((skipped_count, first_skipped))
78
85
 
79
86
  # Iterate through existing IPs and annotate free ranges
80
87
  for ip in ipaddress_list:
81
88
  if prev_ip:
82
- diff = int(ip.address.ip - prev_ip.address.ip)
89
+ diff = ip.address.ip.value - prev_ip.address.ip.value
83
90
  if diff > 1:
84
91
  first_skipped = f"{prev_ip.address.ip + 1}/{prefix.prefixlen}"
85
92
  output.append((diff - 1, first_skipped))
@@ -88,7 +95,7 @@ def add_available_ipaddresses(prefix: netaddr.IPNetwork, ipaddress_list: Iterabl
88
95
 
89
96
  # Include any remaining available IPs
90
97
  if prev_ip.address.ip < last_ip_in_prefix:
91
- skipped_count = int(last_ip_in_prefix - prev_ip.address.ip)
98
+ skipped_count = last_ip_in_prefix.value - prev_ip.address.ip.value
92
99
  first_skipped = f"{prev_ip.address.ip + 1}/{prefix.prefixlen}"
93
100
  output.append((skipped_count, first_skipped))
94
101