nautobot 2.3.13__py3-none-any.whl → 2.3.15__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 (315) hide show
  1. nautobot/circuits/tables.py +2 -1
  2. nautobot/core/filters.py +2 -0
  3. nautobot/core/jobs/cleanup.py +47 -11
  4. nautobot/core/settings.py +3 -0
  5. nautobot/core/settings.yaml +8 -0
  6. nautobot/core/templates/inc/media.html +3 -0
  7. nautobot/core/templates/nautobot_config.py.j2 +3 -0
  8. nautobot/core/templates/search.html +7 -0
  9. nautobot/core/testing/filters.py +20 -5
  10. nautobot/core/testing/mixins.py +7 -2
  11. nautobot/core/tests/integration/test_app_home.py +0 -1
  12. nautobot/core/tests/integration/test_app_navbar.py +0 -1
  13. nautobot/core/tests/integration/test_filters.py +0 -2
  14. nautobot/core/tests/integration/test_home.py +0 -1
  15. nautobot/core/tests/integration/test_navbar.py +0 -1
  16. nautobot/core/tests/integration/test_view_authentication.py +1 -0
  17. nautobot/core/tests/test_views.py +29 -0
  18. nautobot/core/urls.py +9 -0
  19. nautobot/dcim/forms.py +6 -5
  20. nautobot/dcim/models/devices.py +1 -0
  21. nautobot/dcim/tests/test_forms.py +51 -2
  22. nautobot/extras/forms/mixins.py +11 -3
  23. nautobot/extras/jobs.py +6 -4
  24. nautobot/extras/models/customfields.py +12 -11
  25. nautobot/extras/tests/integration/test_plugin_banner.py +0 -2
  26. nautobot/extras/tests/test_forms.py +20 -1
  27. nautobot/ipam/api/views.py +17 -15
  28. nautobot/ipam/models.py +62 -11
  29. nautobot/ipam/tables.py +2 -2
  30. nautobot/ipam/tests/test_api.py +402 -2
  31. nautobot/ipam/tests/test_models.py +41 -0
  32. nautobot/project-static/docs/404.html +2 -2
  33. nautobot/project-static/docs/apps/index.html +2 -2
  34. nautobot/project-static/docs/apps/nautobot-apps.html +2 -2
  35. nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js → bundle.88dd0f4e.min.js} +2 -2
  36. nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js.map → bundle.88dd0f4e.min.js.map} +2 -2
  37. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +2 -2
  38. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +2 -2
  39. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +2 -2
  40. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +2 -2
  41. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +2 -2
  42. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +2 -2
  43. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +2 -2
  44. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +2 -2
  45. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +2 -2
  46. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +2 -2
  47. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +2 -2
  48. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2 -2
  49. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +2 -2
  50. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +8 -6
  51. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2 -2
  52. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +2 -2
  53. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +2 -2
  54. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +2 -2
  55. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +12 -5
  56. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2 -2
  57. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +2 -2
  58. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +2 -2
  59. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2 -2
  60. nautobot/project-static/docs/development/apps/api/configuration-view.html +2 -2
  61. nautobot/project-static/docs/development/apps/api/database-backend-config.html +2 -2
  62. nautobot/project-static/docs/development/apps/api/models/django-admin.html +2 -2
  63. nautobot/project-static/docs/development/apps/api/models/global-search.html +2 -2
  64. nautobot/project-static/docs/development/apps/api/models/graphql.html +2 -2
  65. nautobot/project-static/docs/development/apps/api/models/index.html +2 -2
  66. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +2 -2
  67. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +2 -2
  68. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +2 -2
  69. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +2 -2
  70. nautobot/project-static/docs/development/apps/api/platform-features/index.html +2 -2
  71. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +2 -2
  72. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +2 -2
  73. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +2 -2
  74. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +2 -2
  75. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +2 -2
  76. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +2 -2
  77. nautobot/project-static/docs/development/apps/api/prometheus.html +2 -2
  78. nautobot/project-static/docs/development/apps/api/setup.html +2 -2
  79. nautobot/project-static/docs/development/apps/api/testing.html +2 -2
  80. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +2 -2
  81. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +2 -2
  82. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +2 -2
  83. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +2 -2
  84. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +2 -2
  85. nautobot/project-static/docs/development/apps/api/views/base-template.html +2 -2
  86. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +2 -2
  87. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +2 -2
  88. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +2 -2
  89. nautobot/project-static/docs/development/apps/api/views/index.html +2 -2
  90. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +2 -2
  91. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +2 -2
  92. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +2 -2
  93. nautobot/project-static/docs/development/apps/api/views/notes.html +2 -2
  94. nautobot/project-static/docs/development/apps/api/views/rest-api.html +2 -2
  95. nautobot/project-static/docs/development/apps/api/views/urls.html +2 -2
  96. nautobot/project-static/docs/development/apps/index.html +2 -2
  97. nautobot/project-static/docs/development/apps/migration/code-updates.html +2 -2
  98. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +2 -2
  99. nautobot/project-static/docs/development/apps/migration/from-v1.html +2 -2
  100. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +2 -2
  101. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +2 -2
  102. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +2 -2
  103. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +2 -2
  104. nautobot/project-static/docs/development/apps/porting-from-netbox.html +2 -2
  105. nautobot/project-static/docs/development/core/application-registry.html +2 -2
  106. nautobot/project-static/docs/development/core/best-practices.html +2 -2
  107. nautobot/project-static/docs/development/core/bootstrap-ui.html +2 -2
  108. nautobot/project-static/docs/development/core/caching.html +2 -2
  109. nautobot/project-static/docs/development/core/controllers.html +2 -2
  110. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +27 -70
  111. nautobot/project-static/docs/development/core/generic-views.html +2 -2
  112. nautobot/project-static/docs/development/core/getting-started.html +155 -141
  113. nautobot/project-static/docs/development/core/homepage.html +2 -2
  114. nautobot/project-static/docs/development/core/index.html +2 -2
  115. nautobot/project-static/docs/development/core/model-checklist.html +2 -2
  116. nautobot/project-static/docs/development/core/model-features.html +2 -2
  117. nautobot/project-static/docs/development/core/natural-keys.html +2 -2
  118. nautobot/project-static/docs/development/core/navigation-menu.html +2 -2
  119. nautobot/project-static/docs/development/core/release-checklist.html +2 -2
  120. nautobot/project-static/docs/development/core/role-internals.html +2 -2
  121. nautobot/project-static/docs/development/core/settings.html +2 -2
  122. nautobot/project-static/docs/development/core/style-guide.html +2 -2
  123. nautobot/project-static/docs/development/core/templates.html +2 -2
  124. nautobot/project-static/docs/development/core/testing.html +2 -2
  125. nautobot/project-static/docs/development/core/user-preferences.html +2 -2
  126. nautobot/project-static/docs/development/index.html +2 -2
  127. nautobot/project-static/docs/development/jobs/index.html +143 -119
  128. nautobot/project-static/docs/development/jobs/migration/from-v1.html +2 -2
  129. nautobot/project-static/docs/index.html +2 -2
  130. nautobot/project-static/docs/overview/application_stack.html +2 -2
  131. nautobot/project-static/docs/overview/design_philosophy.html +2 -2
  132. nautobot/project-static/docs/release-notes/index.html +2 -2
  133. nautobot/project-static/docs/release-notes/version-1.0.html +2 -2
  134. nautobot/project-static/docs/release-notes/version-1.1.html +2 -2
  135. nautobot/project-static/docs/release-notes/version-1.2.html +2 -2
  136. nautobot/project-static/docs/release-notes/version-1.3.html +2 -2
  137. nautobot/project-static/docs/release-notes/version-1.4.html +2 -2
  138. nautobot/project-static/docs/release-notes/version-1.5.html +2 -2
  139. nautobot/project-static/docs/release-notes/version-1.6.html +2 -2
  140. nautobot/project-static/docs/release-notes/version-2.0.html +2 -2
  141. nautobot/project-static/docs/release-notes/version-2.1.html +2 -2
  142. nautobot/project-static/docs/release-notes/version-2.2.html +2 -2
  143. nautobot/project-static/docs/release-notes/version-2.3.html +536 -216
  144. nautobot/project-static/docs/requirements.txt +2 -2
  145. nautobot/project-static/docs/search/search_index.json +1 -1
  146. nautobot/project-static/docs/sitemap.xml +270 -270
  147. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  148. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +2 -2
  149. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +2 -2
  150. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +2 -2
  151. nautobot/project-static/docs/user-guide/administration/configuration/index.html +2 -2
  152. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +2 -2
  153. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +29 -2
  154. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +2 -2
  155. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +2 -2
  156. nautobot/project-static/docs/user-guide/administration/guides/docker.html +2 -2
  157. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +2 -2
  158. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +2 -2
  159. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +2 -2
  160. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +2 -2
  161. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +2 -2
  162. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +2 -2
  163. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +2 -2
  164. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +2 -2
  165. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +2 -2
  166. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +2 -2
  167. nautobot/project-static/docs/user-guide/administration/installation/index.html +2 -2
  168. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +2 -2
  169. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +2 -2
  170. nautobot/project-static/docs/user-guide/administration/installation/services.html +2 -2
  171. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +2 -2
  172. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +2 -2
  173. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +2 -2
  174. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +2 -2
  175. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +2 -2
  176. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +2 -2
  177. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +2 -2
  178. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +2 -2
  179. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +2 -2
  180. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +2 -2
  181. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +2 -2
  182. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +2 -2
  183. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +2 -2
  184. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +2 -2
  185. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +2 -2
  186. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +2 -2
  187. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +2 -2
  188. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +2 -2
  189. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +2 -2
  190. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +2 -2
  191. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +2 -2
  192. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +2 -2
  193. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +2 -2
  194. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +2 -2
  195. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +2 -2
  196. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +2 -2
  197. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +2 -2
  198. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +2 -2
  199. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +2 -2
  200. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +2 -2
  201. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +2 -2
  202. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +2 -2
  203. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +2 -2
  204. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +2 -2
  205. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +2 -2
  206. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +2 -2
  207. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +2 -2
  208. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +2 -2
  209. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +2 -2
  210. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +2 -2
  211. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +2 -2
  212. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +2 -2
  213. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +2 -2
  214. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +2 -2
  215. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +2 -2
  216. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +2 -2
  217. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +2 -2
  218. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +2 -2
  219. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +2 -2
  220. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +2 -2
  221. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +2 -2
  222. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +2 -2
  223. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +2 -2
  224. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +2 -2
  225. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +2 -2
  226. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +2 -2
  227. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +2 -2
  228. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +2 -2
  229. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +2 -2
  230. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +2 -2
  231. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +2 -2
  232. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +2 -2
  233. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +2 -2
  234. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +2 -2
  235. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +2 -2
  236. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +2 -2
  237. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +2 -2
  238. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +2 -2
  239. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +2 -2
  240. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +2 -2
  241. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +2 -2
  242. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +2 -2
  243. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +2 -2
  244. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +2 -2
  245. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +2 -2
  246. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +2 -2
  247. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +2 -2
  248. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +2 -2
  249. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +2 -2
  250. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +2 -2
  251. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +2 -2
  252. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +2 -2
  253. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +2 -2
  254. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +2 -2
  255. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +2 -2
  256. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +2 -2
  257. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +2 -2
  258. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +2 -2
  259. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +2 -2
  260. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +2 -2
  261. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +2 -2
  262. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +2 -2
  263. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +2 -2
  264. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +2 -2
  265. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +2 -2
  266. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +2 -2
  267. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +2 -2
  268. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +2 -2
  269. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +2 -2
  270. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +2 -2
  271. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +2 -2
  272. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +2 -2
  273. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +2 -2
  274. nautobot/project-static/docs/user-guide/index.html +2 -2
  275. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +2 -2
  276. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +2 -2
  277. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +2 -2
  278. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +2 -2
  279. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +2 -2
  280. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +2 -2
  281. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +2 -2
  282. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +2 -2
  283. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +2 -2
  284. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +2 -2
  285. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +2 -2
  286. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +2 -2
  287. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +2 -2
  288. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +2 -2
  289. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +2 -2
  290. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +2 -2
  291. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +2 -2
  292. nautobot/project-static/docs/user-guide/platform-functionality/note.html +2 -2
  293. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +2 -2
  294. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +2 -2
  295. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +2 -2
  296. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +2 -2
  297. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +2 -2
  298. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +2 -2
  299. nautobot/project-static/docs/user-guide/platform-functionality/role.html +2 -2
  300. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +2 -2
  301. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +2 -2
  302. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +2 -2
  303. nautobot/project-static/docs/user-guide/platform-functionality/status.html +2 -2
  304. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +2 -2
  305. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +2 -2
  306. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +2 -2
  307. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +2 -2
  308. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +2 -2
  309. {nautobot-2.3.13.dist-info → nautobot-2.3.15.dist-info}/METADATA +5 -4
  310. {nautobot-2.3.13.dist-info → nautobot-2.3.15.dist-info}/RECORD +314 -315
  311. nautobot/core/fixtures/user-data.json +0 -59
  312. {nautobot-2.3.13.dist-info → nautobot-2.3.15.dist-info}/LICENSE.txt +0 -0
  313. {nautobot-2.3.13.dist-info → nautobot-2.3.15.dist-info}/NOTICE +0 -0
  314. {nautobot-2.3.13.dist-info → nautobot-2.3.15.dist-info}/WHEEL +0 -0
  315. {nautobot-2.3.13.dist-info → nautobot-2.3.15.dist-info}/entry_points.txt +0 -0
@@ -571,6 +571,126 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
571
571
  self.assertIn("prefixcf", response.data[0]["custom_fields"])
572
572
  self.assertEqual("value 1", response.data[0]["custom_fields"]["prefixcf"])
573
573
 
574
+ def test_create_available_prefixes_with_permissions_constraint(self):
575
+ # Prepare prefix and permissions
576
+ prefix = Prefix.objects.create(
577
+ prefix="10.2.3.0/24",
578
+ type=choices.PrefixTypeChoices.TYPE_POOL,
579
+ namespace=self.namespace,
580
+ status=self.status,
581
+ description="This is the Prefix created for whole network.",
582
+ )
583
+ url = reverse("ipam-api:prefix-available-prefixes", kwargs={"pk": prefix.pk})
584
+ self.add_permissions("ipam.view_prefix")
585
+ self.add_permissions(
586
+ "ipam.add_prefix", constraints={"description__startswith": "This is the Prefix created for"}
587
+ )
588
+
589
+ # Test invalid request
590
+ data = {
591
+ "prefix_length": 26,
592
+ "status": self.status.pk,
593
+ }
594
+ invalid_data_list = [
595
+ data,
596
+ {**data, "description": ""},
597
+ {**data, "description": "Some description"},
598
+ {**data, "description": "Some description. This is the IP created for"},
599
+ ]
600
+
601
+ for invalid_data in invalid_data_list:
602
+ with self.subTest(case=invalid_data):
603
+ response = self.client.post(url, invalid_data, format="json", **self.header)
604
+ self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
605
+ self.assertIn("detail", response.data)
606
+ self.assertEqual(response.data["detail"], "You do not have permission to perform this action.")
607
+
608
+ # Verify that no prefixes were created (the entire prefix is still available)
609
+ response = self.client.get(url, **self.header)
610
+ self.assertHttpStatus(response, status.HTTP_200_OK)
611
+ self.assertEqual(len(response.data), 1)
612
+ self.assertEqual(response.data[0]["prefix"], prefix.cidr_str)
613
+
614
+ # Test valid request
615
+ valid_data = {
616
+ "prefix_length": 26,
617
+ "status": self.status.pk,
618
+ "description": "This is the Prefix created for my local network",
619
+ }
620
+ response = self.client.post(url, valid_data, format="json", **self.header)
621
+ self.assertHttpStatus(response, status.HTTP_201_CREATED)
622
+ self.assertEqual(response.data["prefix"], "10.2.3.0/26")
623
+
624
+ # Verify that prefix is created
625
+ response = self.client.get(url, **self.header)
626
+ self.assertHttpStatus(response, status.HTTP_200_OK)
627
+ self.assertEqual(len(response.data), 2)
628
+ self.assertEqual(response.data[0]["prefix"], "10.2.3.64/26")
629
+ self.assertEqual(response.data[1]["prefix"], "10.2.3.128/25")
630
+
631
+ def test_create_multiple_available_prefixes_with_permissions_constraint(self):
632
+ # Prepare prefix and permissions
633
+ prefix = Prefix.objects.create(
634
+ prefix="10.2.3.0/24",
635
+ type=choices.PrefixTypeChoices.TYPE_POOL,
636
+ namespace=self.namespace,
637
+ status=self.status,
638
+ description="This is the Prefix created for whole network.",
639
+ )
640
+ url = reverse("ipam-api:prefix-available-prefixes", kwargs={"pk": prefix.pk})
641
+ self.add_permissions("ipam.view_prefix")
642
+ self.add_permissions(
643
+ "ipam.add_prefix", constraints={"description__startswith": "This is the Prefix created for"}
644
+ )
645
+
646
+ # Test invalid request
647
+ data = [
648
+ {
649
+ "prefix_length": 26,
650
+ "status": self.status.pk,
651
+ "description": "This is the Prefix created for my local network",
652
+ },
653
+ {
654
+ "prefix_length": 26,
655
+ "status": self.status.pk,
656
+ },
657
+ ]
658
+ response = self.client.post(url, data, format="json", **self.header)
659
+ self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
660
+ self.assertIn("detail", response.data)
661
+ self.assertEqual(response.data["detail"], "You do not have permission to perform this action.")
662
+
663
+ # Verify that no prefixes were created (the entire prefix is still available)
664
+ response = self.client.get(url, **self.header)
665
+ self.assertHttpStatus(response, status.HTTP_200_OK)
666
+ self.assertEqual(len(response.data), 1)
667
+ self.assertEqual(response.data[0]["prefix"], prefix.cidr_str)
668
+
669
+ # Test valid request
670
+ data = [
671
+ {
672
+ "prefix_length": 26,
673
+ "status": self.status.pk,
674
+ "description": "This is the Prefix created for my local network",
675
+ },
676
+ {
677
+ "prefix_length": 26,
678
+ "status": self.status.pk,
679
+ "description": "This is the Prefix created for my guest house network",
680
+ },
681
+ ]
682
+ response = self.client.post(url, data, format="json", **self.header)
683
+ self.assertHttpStatus(response, status.HTTP_201_CREATED)
684
+ self.assertEqual(len(response.data), 2)
685
+ self.assertEqual(response.data[0]["prefix"], "10.2.3.0/26")
686
+ self.assertEqual(response.data[1]["prefix"], "10.2.3.64/26")
687
+
688
+ # Verify that prefixes were created
689
+ response = self.client.get(url, **self.header)
690
+ self.assertHttpStatus(response, status.HTTP_200_OK)
691
+ self.assertEqual(len(response.data), 1)
692
+ self.assertEqual(response.data[0]["prefix"], "10.2.3.128/25")
693
+
574
694
  def test_list_available_ips(self):
575
695
  """
576
696
  Test retrieval of all available IP addresses within a parent prefix.
@@ -594,9 +714,76 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
594
714
  response = self.client.get(url, **self.header)
595
715
  self.assertEqual(len(response.data), 6) # 8 - 2 because prefix.type = network
596
716
 
717
+ def test_list_available_ips_calculate_child_ips(self):
718
+ """
719
+ Test retrieval of all available IP addresses when child IP's exists.
720
+ """
721
+ ip_status = Status.objects.get_for_model(IPAddress).first()
722
+ prefix = Prefix.objects.create(
723
+ prefix="192.0.3.0/29",
724
+ type=choices.PrefixTypeChoices.TYPE_POOL,
725
+ namespace=self.namespace,
726
+ status=self.status,
727
+ )
728
+ Prefix.objects.create(
729
+ prefix="192.0.3.0/30",
730
+ type=choices.PrefixTypeChoices.TYPE_POOL,
731
+ namespace=self.namespace,
732
+ status=self.status,
733
+ )
734
+ IPAddress.objects.create(
735
+ address="192.0.3.1/30",
736
+ status=ip_status,
737
+ namespace=self.namespace,
738
+ )
739
+
740
+ url = reverse("ipam-api:prefix-available-ips", kwargs={"pk": prefix.pk})
741
+ self.add_permissions("ipam.view_prefix", "ipam.view_ipaddress")
742
+
743
+ # Retrieve all available IPs
744
+ response = self.client.get(url, **self.header)
745
+ self.assertEqual(len(response.data), 7) # 7 because prefix.type = pool got 8 IP's minus one children IP
746
+
747
+ def test_create_single_available_ip_calculate_child_ips(self):
748
+ """
749
+ Test creating a single IP when child IP's exists.
750
+ """
751
+ ip_status = Status.objects.get_for_model(IPAddress).first()
752
+ prefix = Prefix.objects.create(
753
+ prefix="192.0.4.0/31",
754
+ namespace=self.namespace,
755
+ type=choices.PrefixTypeChoices.TYPE_NETWORK,
756
+ status=self.status,
757
+ )
758
+ Prefix.objects.create(
759
+ prefix="192.0.4.0/32",
760
+ type=choices.PrefixTypeChoices.TYPE_POOL,
761
+ namespace=self.namespace,
762
+ status=self.status,
763
+ )
764
+ IPAddress.objects.create(
765
+ address="192.0.4.0/32",
766
+ status=ip_status,
767
+ namespace=self.namespace,
768
+ )
769
+ url = reverse("ipam-api:prefix-available-ips", kwargs={"pk": prefix.pk})
770
+ self.add_permissions("ipam.view_prefix", "ipam.add_ipaddress", "extras.view_status")
771
+
772
+ data = {
773
+ "status": self.status.pk,
774
+ }
775
+
776
+ response = self.client.post(url, data, format="json", **self.header)
777
+ self.assertHttpStatus(response, status.HTTP_201_CREATED)
778
+ self.assertEqual(str(response.data["parent"]["url"]), self.absolute_api_url(prefix))
779
+
780
+ response = self.client.post(url, data, format="json", **self.header)
781
+ self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
782
+ self.assertIn("detail", response.data)
783
+
597
784
  def test_create_single_available_ip(self):
598
785
  """
599
- Test retrieval of the first available IP address within a parent prefix.
786
+ Test creating single IP will return 204 No content when pool is fully filled.
600
787
  """
601
788
  prefix = Prefix.objects.create(
602
789
  prefix="192.0.2.0/29",
@@ -664,6 +851,116 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
664
851
  self.assertIn("ipcf", response.data[0]["custom_fields"])
665
852
  self.assertEqual("1", response.data[0]["custom_fields"]["ipcf"])
666
853
 
854
+ def test_create_available_ips_with_permissions_constraint(self):
855
+ # Prepare prefix and permissions
856
+ prefix = Prefix.objects.create(
857
+ prefix="192.168.0.0/30",
858
+ type=choices.PrefixTypeChoices.TYPE_NETWORK,
859
+ namespace=self.namespace,
860
+ status=self.status,
861
+ description="This is the Prefix created for whole network.",
862
+ )
863
+ url = reverse("ipam-api:prefix-available-ips", kwargs={"pk": prefix.pk})
864
+ self.add_permissions("ipam.view_prefix", "ipam.view_ipaddress")
865
+ self.add_permissions(
866
+ "ipam.add_ipaddress", constraints={"description__startswith": "This is the IP created for"}
867
+ )
868
+
869
+ # Test invalid request
870
+ data = {
871
+ "status": self.status.pk,
872
+ }
873
+ invalid_data_list = [
874
+ data,
875
+ {**data, "description": ""},
876
+ {**data, "description": "Some description"},
877
+ {**data, "description": "Some description. This is the IP created for"},
878
+ ]
879
+
880
+ for invalid_data in invalid_data_list:
881
+ with self.subTest(case=invalid_data):
882
+ response = self.client.post(url, invalid_data, format="json", **self.header)
883
+ self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
884
+ self.assertIn("detail", response.data)
885
+ self.assertEqual(response.data["detail"], "You do not have permission to perform this action.")
886
+
887
+ # Verify that no IPs were created (the entire prefix pool is still available)
888
+ response = self.client.get(url, **self.header)
889
+ self.assertHttpStatus(response, status.HTTP_200_OK)
890
+ self.assertEqual(len(response.data), 2)
891
+
892
+ # Test valid request
893
+ valid_data = {
894
+ "status": self.status.pk,
895
+ "description": "This is the IP created for my private laptop",
896
+ }
897
+ response = self.client.post(url, valid_data, format="json", **self.header)
898
+ self.assertHttpStatus(response, status.HTTP_201_CREATED)
899
+ self.assertEqual(response.data["address"], "192.168.0.1/30")
900
+
901
+ # Verify that IP is created
902
+ response = self.client.get(url, **self.header)
903
+ self.assertHttpStatus(response, status.HTTP_200_OK)
904
+ self.assertEqual(len(response.data), 1)
905
+ self.assertEqual(response.data[0]["address"], "192.168.0.2/30")
906
+
907
+ def test_create_multiple_available_ips_with_permissions_constraint(self):
908
+ # Prepare prefix and permissions
909
+ prefix = Prefix.objects.create(
910
+ prefix="192.168.0.0/30",
911
+ type=choices.PrefixTypeChoices.TYPE_NETWORK,
912
+ namespace=self.namespace,
913
+ status=self.status,
914
+ description="This is a Prefix created for whole network.",
915
+ )
916
+ url = reverse("ipam-api:prefix-available-ips", kwargs={"pk": prefix.pk})
917
+ self.add_permissions("ipam.view_prefix", "ipam.view_ipaddress")
918
+ self.add_permissions(
919
+ "ipam.add_ipaddress", constraints={"description__startswith": "This is the IP created for"}
920
+ )
921
+
922
+ # Test invalid request
923
+ data = [
924
+ {
925
+ "status": self.status.pk,
926
+ },
927
+ {
928
+ "status": self.status.pk,
929
+ "description": "This is an IP created for my private laptop",
930
+ },
931
+ ]
932
+ response = self.client.post(url, data, format="json", **self.header)
933
+ self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
934
+ self.assertIn("detail", response.data)
935
+ self.assertEqual(response.data["detail"], "You do not have permission to perform this action.")
936
+
937
+ # Verify that no IPs were created (the entire prefix pool is still available)
938
+ response = self.client.get(url, **self.header)
939
+ self.assertHttpStatus(response, status.HTTP_200_OK)
940
+ self.assertEqual(len(response.data), 2)
941
+
942
+ # Test valid request
943
+ valid_data = [
944
+ {
945
+ "status": self.status.pk,
946
+ "description": "This is the IP created for my private laptop",
947
+ },
948
+ {
949
+ "status": self.status.pk,
950
+ "description": "This is the IP created for my gaming laptop",
951
+ },
952
+ ]
953
+ response = self.client.post(url, valid_data, format="json", **self.header)
954
+ self.assertHttpStatus(response, status.HTTP_201_CREATED)
955
+ self.assertEqual(len(response.data), 2)
956
+ self.assertEqual(response.data[0]["address"], "192.168.0.1/30")
957
+ self.assertEqual(response.data[1]["address"], "192.168.0.2/30")
958
+
959
+ # Verify that IPs are created
960
+ response = self.client.get(url, **self.header)
961
+ self.assertHttpStatus(response, status.HTTP_200_OK)
962
+ self.assertEqual(len(response.data), 0)
963
+
667
964
 
668
965
  class PrefixLocationAssignmentTest(APIViewTestCases.APIViewTestCase):
669
966
  model = PrefixLocationAssignment
@@ -1022,7 +1319,7 @@ class VLANGroupTest(APIViewTestCases.APIViewTestCase):
1022
1319
  Test retrieval of all available VLAN IDs within a VLANGroup.
1023
1320
  """
1024
1321
  url = reverse("ipam-api:vlangroup-available-vlans", kwargs={"pk": self.vlan_group.pk})
1025
- self.add_permissions("ipam.view_vlangroup")
1322
+ self.add_permissions("ipam.view_vlangroup", "ipam.view_vlan")
1026
1323
 
1027
1324
  # Retrieve all available VLAN IDs
1028
1325
  response = self.client.get(url, **self.header)
@@ -1039,6 +1336,7 @@ class VLANGroupTest(APIViewTestCases.APIViewTestCase):
1039
1336
  url = reverse("ipam-api:vlangroup-available-vlans", kwargs={"pk": self.vlan_group.pk})
1040
1337
  self.add_permissions(
1041
1338
  "ipam.view_vlangroup",
1339
+ "ipam.view_vlan",
1042
1340
  "ipam.add_vlan",
1043
1341
  )
1044
1342
 
@@ -1080,6 +1378,7 @@ class VLANGroupTest(APIViewTestCases.APIViewTestCase):
1080
1378
  url = reverse("ipam-api:vlangroup-available-vlans", kwargs={"pk": self.vlan_group.pk})
1081
1379
  self.add_permissions(
1082
1380
  "ipam.view_vlangroup",
1381
+ "ipam.view_vlan",
1083
1382
  "ipam.add_vlan",
1084
1383
  )
1085
1384
 
@@ -1126,6 +1425,7 @@ class VLANGroupTest(APIViewTestCases.APIViewTestCase):
1126
1425
  url = reverse("ipam-api:vlangroup-available-vlans", kwargs={"pk": self.vlan_group.pk})
1127
1426
  self.add_permissions(
1128
1427
  "ipam.view_vlangroup",
1428
+ "ipam.view_vlan",
1129
1429
  "ipam.add_vlan",
1130
1430
  )
1131
1431
 
@@ -1155,6 +1455,7 @@ class VLANGroupTest(APIViewTestCases.APIViewTestCase):
1155
1455
  url = reverse("ipam-api:vlangroup-available-vlans", kwargs={"pk": self.vlan_group.pk})
1156
1456
  self.add_permissions(
1157
1457
  "ipam.view_vlangroup",
1458
+ "ipam.view_vlan",
1158
1459
  "ipam.add_vlan",
1159
1460
  )
1160
1461
 
@@ -1182,6 +1483,105 @@ class VLANGroupTest(APIViewTestCases.APIViewTestCase):
1182
1483
  response.data["detail"],
1183
1484
  )
1184
1485
 
1486
+ def test_create_available_vlans_with_permissions_constraint(self):
1487
+ url = reverse("ipam-api:vlangroup-available-vlans", kwargs={"pk": self.vlan_group.pk})
1488
+ self.add_permissions(
1489
+ "ipam.view_vlangroup",
1490
+ "ipam.view_vlan",
1491
+ )
1492
+ self.add_permissions("ipam.add_vlan", constraints={"description__startswith": "This is the VLAN created for"})
1493
+
1494
+ data = {"name": "VLAN_6", "status": self.default_status.pk, "vid": 6}
1495
+ invalid_data_list = [
1496
+ data,
1497
+ {**data, "description": ""},
1498
+ {**data, "description": "Some description"},
1499
+ {**data, "description": "Some description. This is the VLAN created for"},
1500
+ ]
1501
+
1502
+ # Test invalid request
1503
+ for invalid_data in invalid_data_list:
1504
+ with self.subTest(case=invalid_data):
1505
+ response = self.client.post(url, data, format="json", **self.header)
1506
+ self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
1507
+ self.assertIn("detail", response.data)
1508
+ self.assertEqual(response.data["detail"], "You do not have permission to perform this action.")
1509
+
1510
+ # Verify that no VLANs were created (number of VLANs is the same as on the beginning of the test)
1511
+ response = self.client.get(url, **self.header)
1512
+ self.assertHttpStatus(response, status.HTTP_200_OK)
1513
+ self.assertEqual(len(response.data["results"]), len(self.unused_vids))
1514
+
1515
+ # Test valid request
1516
+ valid_data = {
1517
+ "name": "VLAN_6",
1518
+ "status": self.default_status.pk,
1519
+ "vid": 6,
1520
+ "description": "This is the VLAN created for home automation.",
1521
+ }
1522
+ response = self.client.post(url, valid_data, format="json", **self.header)
1523
+ self.assertHttpStatus(response, status.HTTP_201_CREATED)
1524
+ self.assertEqual(response.data["results"]["name"], valid_data["name"])
1525
+
1526
+ # Verify that VLAN is created
1527
+ response = self.client.get(url, **self.header)
1528
+ self.assertHttpStatus(response, status.HTTP_200_OK)
1529
+ self.assertEqual(
1530
+ len(response.data["results"]), len(self.unused_vids) - 1
1531
+ ) # initial unsued vids minus one created
1532
+
1533
+ def test_create_multiple_available_vlans_with_permissions_constraint(self):
1534
+ url = reverse("ipam-api:vlangroup-available-vlans", kwargs={"pk": self.vlan_group.pk})
1535
+ self.add_permissions(
1536
+ "ipam.view_vlangroup",
1537
+ "ipam.view_vlan",
1538
+ )
1539
+ self.add_permissions("ipam.add_vlan", constraints={"description__startswith": "This is the VLAN created for"})
1540
+
1541
+ # Test invalid request
1542
+ data = [
1543
+ {"name": "VLAN_6", "status": self.default_status.pk},
1544
+ {"name": "VLAN_7", "status": self.default_status.pk},
1545
+ {"name": "VLAN_8", "status": self.default_status.pk},
1546
+ ]
1547
+ response = self.client.post(url, data, format="json", **self.header)
1548
+ self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
1549
+ self.assertIn("detail", response.data)
1550
+ self.assertEqual(response.data["detail"], "You do not have permission to perform this action.")
1551
+
1552
+ # Verify that no VLANs were created (number of VLANs is the same as on the beginning of the test)
1553
+ response = self.client.get(url, **self.header)
1554
+ self.assertHttpStatus(response, status.HTTP_200_OK)
1555
+ self.assertEqual(len(response.data["results"]), len(self.unused_vids))
1556
+
1557
+ # Test valid request
1558
+ valid_data = [
1559
+ {
1560
+ "name": "VLAN_6",
1561
+ "status": self.default_status.pk,
1562
+ "description": "This is the VLAN created for home automation.",
1563
+ },
1564
+ {
1565
+ "name": "VLAN_7",
1566
+ "status": self.default_status.pk,
1567
+ "description": "This is the VLAN created for IP cameras.",
1568
+ },
1569
+ {"name": "VLAN_8", "status": self.default_status.pk, "description": "This is the VLAN created for guests."},
1570
+ ]
1571
+ response = self.client.post(url, valid_data, format="json", **self.header)
1572
+ self.assertHttpStatus(response, status.HTTP_201_CREATED)
1573
+ self.assertHttpStatus(response, status.HTTP_201_CREATED)
1574
+ self.assertEqual(len(response.data["results"]), 3)
1575
+ for i, vlan_data in enumerate(data):
1576
+ self.assertEqual(response.data["results"][i]["name"], vlan_data["name"])
1577
+
1578
+ # Verify that VLANs are created
1579
+ response = self.client.get(url, **self.header)
1580
+ self.assertHttpStatus(response, status.HTTP_200_OK)
1581
+ self.assertEqual(
1582
+ len(response.data["results"]), len(self.unused_vids) - 3
1583
+ ) # initial unsued vids minus three created
1584
+
1185
1585
 
1186
1586
  class VLANTest(APIViewTestCases.APIViewTestCase):
1187
1587
  model = VLAN
@@ -1,4 +1,5 @@
1
1
  from unittest import skipIf
2
+ from unittest.mock import patch
2
3
 
3
4
  from django.contrib.contenttypes.models import ContentType
4
5
  from django.core.exceptions import ValidationError
@@ -413,6 +414,39 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
413
414
  self.child1 = Prefix.objects.create(prefix="101.102.0.0/26", status=self.status, namespace=self.namespace)
414
415
  self.child2 = Prefix.objects.create(prefix="101.102.0.64/26", status=self.status, namespace=self.namespace)
415
416
 
417
+ def test_parent_exists_after_model_clean(self):
418
+ prefix = Prefix(
419
+ prefix="101.102.0.2/26", status=self.status, namespace=self.namespace, type=PrefixTypeChoices.TYPE_CONTAINER
420
+ )
421
+ prefix.clean()
422
+ self.assertEqual(prefix.parent, self.child1)
423
+
424
+ def test_reparent_subnets_and_reparent_ips_call(self):
425
+ """Assert reparent_subnets and reparent_ips are only called if there is an update to either of network, namespace or prefix_length"""
426
+ prefix_ip = "101.102.0.0/28"
427
+ with self.subTest("Assert reparent_subnets"):
428
+ with patch.object(Prefix, "reparent_subnets", return_value=None) as mock_reparent_subnets:
429
+ Prefix.objects.create(prefix=prefix_ip, status=self.status, namespace=self.namespace)
430
+ mock_reparent_subnets.assert_called_once()
431
+
432
+ with patch.object(Prefix, "reparent_subnets", return_value=None) as mock_reparent_subnets:
433
+ prefix = Prefix.objects.get(prefix=prefix_ip)
434
+ prefix.description = "Sample Description"
435
+ prefix.save()
436
+ mock_reparent_subnets.assert_not_called()
437
+ prefix.delete()
438
+
439
+ with self.subTest("Assert reparent_ips"):
440
+ with patch.object(Prefix, "reparent_ips", return_value=None) as reparent_ips:
441
+ Prefix.objects.create(prefix=prefix_ip, status=self.status, namespace=self.namespace)
442
+ reparent_ips.assert_called_once()
443
+
444
+ with patch.object(Prefix, "reparent_ips", return_value=None) as reparent_ips:
445
+ prefix = Prefix.objects.get(prefix=prefix_ip)
446
+ prefix.description = "Sample Description"
447
+ prefix.save()
448
+ reparent_ips.assert_not_called()
449
+
416
450
  def test_location_queries(self):
417
451
  locations = Location.objects.all()[:4]
418
452
  for location in locations:
@@ -766,6 +800,13 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
766
800
  IPAddress.objects.create(address="10.0.0.4/24", status=self.status, namespace=self.namespace)
767
801
  self.assertEqual(parent_prefix.get_first_available_ip(), "10.0.0.5/24")
768
802
 
803
+ def test_get_first_available_ip_calculate_child_ips(self):
804
+ parent_prefix = Prefix.objects.create(prefix="10.0.3.0/29", status=self.status, namespace=self.namespace)
805
+ Prefix.objects.create(prefix="10.0.3.0/30", status=self.status, namespace=self.namespace)
806
+ IPAddress(address="10.0.3.1/30", status=self.status, namespace=self.namespace).save()
807
+
808
+ self.assertEqual(parent_prefix.get_first_available_ip(), "10.0.3.2/29")
809
+
769
810
  def test_get_utilization(self):
770
811
  # Container Prefix
771
812
  prefix = Prefix.objects.create(
@@ -12,7 +12,7 @@
12
12
 
13
13
 
14
14
  <link rel="icon" href="/projects/core/en/stable/assets/favicon.ico">
15
- <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.47">
15
+ <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.49">
16
16
 
17
17
 
18
18
 
@@ -8650,7 +8650,7 @@
8650
8650
  <script id="__config" type="application/json">{"base": "/projects/core/en/stable/", "features": ["content.code.copy", "content.tabs.link", "navigation.footer", "navigation.tabs", "navigation.tabs.sticky", "navigation.tracking", "search.highlight", "search.share", "search.suggest"], "search": "/projects/core/en/stable/assets/javascripts/workers/search.6ce7567c.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
8651
8651
 
8652
8652
 
8653
- <script src="/projects/core/en/stable/assets/javascripts/bundle.83f73b43.min.js"></script>
8653
+ <script src="/projects/core/en/stable/assets/javascripts/bundle.88dd0f4e.min.js"></script>
8654
8654
 
8655
8655
  <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
8656
8656
 
@@ -18,7 +18,7 @@
18
18
 
19
19
 
20
20
  <link rel="icon" href="../assets/favicon.ico">
21
- <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.47">
21
+ <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.49">
22
22
 
23
23
 
24
24
 
@@ -8817,7 +8817,7 @@
8817
8817
  <script id="__config" type="application/json">{"base": "..", "features": ["content.code.copy", "content.tabs.link", "navigation.footer", "navigation.tabs", "navigation.tabs.sticky", "navigation.tracking", "search.highlight", "search.share", "search.suggest"], "search": "../assets/javascripts/workers/search.6ce7567c.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
8818
8818
 
8819
8819
 
8820
- <script src="../assets/javascripts/bundle.83f73b43.min.js"></script>
8820
+ <script src="../assets/javascripts/bundle.88dd0f4e.min.js"></script>
8821
8821
 
8822
8822
  <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
8823
8823
 
@@ -16,7 +16,7 @@
16
16
 
17
17
 
18
18
  <link rel="icon" href="../assets/favicon.ico">
19
- <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.47">
19
+ <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.49">
20
20
 
21
21
 
22
22
 
@@ -8751,7 +8751,7 @@
8751
8751
  <script id="__config" type="application/json">{"base": "..", "features": ["content.code.copy", "content.tabs.link", "navigation.footer", "navigation.tabs", "navigation.tabs.sticky", "navigation.tracking", "search.highlight", "search.share", "search.suggest"], "search": "../assets/javascripts/workers/search.6ce7567c.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
8752
8752
 
8753
8753
 
8754
- <script src="../assets/javascripts/bundle.83f73b43.min.js"></script>
8754
+ <script src="../assets/javascripts/bundle.88dd0f4e.min.js"></script>
8755
8755
 
8756
8756
  <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
8757
8757