nautobot 2.2.6__py3-none-any.whl → 2.2.8__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 (292) hide show
  1. nautobot/core/api/mixins.py +6 -1
  2. nautobot/core/api/schema.py +3 -1
  3. nautobot/core/celery/__init__.py +1 -1
  4. nautobot/core/graphql/generators.py +2 -2
  5. nautobot/core/graphql/schema.py +7 -4
  6. nautobot/core/tests/integration/test_general_functionality.py +36 -0
  7. nautobot/core/tests/runner.py +10 -0
  8. nautobot/core/tests/test_graphql.py +51 -5
  9. nautobot/dcim/models/devices.py +10 -3
  10. nautobot/dcim/tests/test_models.py +75 -0
  11. nautobot/extras/models/groups.py +9 -2
  12. nautobot/extras/models/relationships.py +12 -0
  13. nautobot/extras/tests/integration/test_dynamicgroups.py +1 -1
  14. nautobot/extras/tests/test_dynamicgroups.py +8 -1
  15. nautobot/extras/tests/test_relationships.py +221 -1
  16. nautobot/extras/views.py +1 -1
  17. nautobot/ipam/api/serializers.py +46 -16
  18. nautobot/ipam/api/views.py +29 -16
  19. nautobot/ipam/tests/test_api.py +15 -20
  20. nautobot/project-static/docs/404.html +3 -3
  21. nautobot/project-static/docs/apps/index.html +3 -3
  22. nautobot/project-static/docs/apps/nautobot-apps.html +3 -3
  23. nautobot/project-static/docs/assets/javascripts/{bundle.ad660dcc.min.js → bundle.fe8b6f2b.min.js} +4 -4
  24. nautobot/project-static/docs/assets/javascripts/{bundle.ad660dcc.min.js.map → bundle.fe8b6f2b.min.js.map} +3 -3
  25. nautobot/project-static/docs/assets/stylesheets/{main.6543a935.min.css → main.76a95c52.min.css} +1 -1
  26. nautobot/project-static/docs/assets/stylesheets/{main.6543a935.min.css.map → main.76a95c52.min.css.map} +1 -1
  27. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +3 -3
  28. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +3 -3
  29. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +3 -3
  30. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +3 -3
  31. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +3 -3
  32. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +3 -3
  33. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +3 -3
  34. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +3 -3
  35. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +3 -3
  36. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +3 -3
  37. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +3 -3
  38. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +3 -3
  39. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +3 -3
  40. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +3 -3
  41. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +3 -3
  42. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +3 -3
  43. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +3 -3
  44. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +3 -3
  45. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +3 -3
  46. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +3 -3
  47. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +3 -3
  48. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +3 -3
  49. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +3 -3
  50. nautobot/project-static/docs/development/apps/api/configuration-view.html +3 -3
  51. nautobot/project-static/docs/development/apps/api/database-backend-config.html +3 -3
  52. nautobot/project-static/docs/development/apps/api/models/django-admin.html +3 -3
  53. nautobot/project-static/docs/development/apps/api/models/global-search.html +3 -3
  54. nautobot/project-static/docs/development/apps/api/models/graphql.html +3 -3
  55. nautobot/project-static/docs/development/apps/api/models/index.html +3 -3
  56. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +3 -3
  57. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +3 -3
  58. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +3 -3
  59. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +3 -3
  60. nautobot/project-static/docs/development/apps/api/platform-features/index.html +3 -3
  61. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +3 -3
  62. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +3 -3
  63. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +3 -3
  64. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +3 -3
  65. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +3 -3
  66. nautobot/project-static/docs/development/apps/api/prometheus.html +3 -3
  67. nautobot/project-static/docs/development/apps/api/setup.html +3 -3
  68. nautobot/project-static/docs/development/apps/api/testing.html +3 -3
  69. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +3 -3
  70. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +3 -3
  71. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +3 -3
  72. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +3 -3
  73. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +3 -3
  74. nautobot/project-static/docs/development/apps/api/views/base-template.html +3 -3
  75. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +3 -3
  76. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +3 -3
  77. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +3 -3
  78. nautobot/project-static/docs/development/apps/api/views/index.html +3 -3
  79. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +3 -3
  80. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +3 -3
  81. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +3 -3
  82. nautobot/project-static/docs/development/apps/api/views/notes.html +3 -3
  83. nautobot/project-static/docs/development/apps/api/views/rest-api.html +3 -3
  84. nautobot/project-static/docs/development/apps/api/views/urls.html +3 -3
  85. nautobot/project-static/docs/development/apps/index.html +3 -3
  86. nautobot/project-static/docs/development/apps/migration/code-updates.html +3 -3
  87. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +3 -3
  88. nautobot/project-static/docs/development/apps/migration/from-v1.html +3 -3
  89. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +3 -3
  90. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +3 -3
  91. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +3 -3
  92. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +3 -3
  93. nautobot/project-static/docs/development/apps/porting-from-netbox.html +3 -3
  94. nautobot/project-static/docs/development/core/application-registry.html +3 -3
  95. nautobot/project-static/docs/development/core/best-practices.html +3 -3
  96. nautobot/project-static/docs/development/core/bootstrap-ui.html +3 -3
  97. nautobot/project-static/docs/development/core/caching.html +3 -3
  98. nautobot/project-static/docs/development/core/controllers.html +3 -3
  99. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +3 -3
  100. nautobot/project-static/docs/development/core/generic-views.html +3 -3
  101. nautobot/project-static/docs/development/core/getting-started.html +5 -6
  102. nautobot/project-static/docs/development/core/homepage.html +3 -3
  103. nautobot/project-static/docs/development/core/index.html +3 -3
  104. nautobot/project-static/docs/development/core/model-checklist.html +3 -3
  105. nautobot/project-static/docs/development/core/model-features.html +3 -3
  106. nautobot/project-static/docs/development/core/natural-keys.html +3 -3
  107. nautobot/project-static/docs/development/core/navigation-menu.html +3 -3
  108. nautobot/project-static/docs/development/core/release-checklist.html +3 -3
  109. nautobot/project-static/docs/development/core/role-internals.html +3 -3
  110. nautobot/project-static/docs/development/core/settings.html +3 -3
  111. nautobot/project-static/docs/development/core/style-guide.html +3 -3
  112. nautobot/project-static/docs/development/core/templates.html +3 -3
  113. nautobot/project-static/docs/development/core/testing.html +16 -4
  114. nautobot/project-static/docs/development/core/user-preferences.html +3 -3
  115. nautobot/project-static/docs/development/index.html +3 -3
  116. nautobot/project-static/docs/development/jobs/index.html +3 -3
  117. nautobot/project-static/docs/development/jobs/migration/from-v1.html +3 -3
  118. nautobot/project-static/docs/objects.inv +0 -0
  119. nautobot/project-static/docs/overview/application_stack.html +3 -3
  120. nautobot/project-static/docs/overview/design_philosophy.html +3 -3
  121. nautobot/project-static/docs/overview/index.html +3 -3
  122. nautobot/project-static/docs/release-notes/index.html +3 -3
  123. nautobot/project-static/docs/release-notes/version-1.0.html +3 -3
  124. nautobot/project-static/docs/release-notes/version-1.1.html +3 -3
  125. nautobot/project-static/docs/release-notes/version-1.2.html +3 -3
  126. nautobot/project-static/docs/release-notes/version-1.3.html +3 -3
  127. nautobot/project-static/docs/release-notes/version-1.4.html +3 -3
  128. nautobot/project-static/docs/release-notes/version-1.5.html +3 -3
  129. nautobot/project-static/docs/release-notes/version-1.6.html +3 -3
  130. nautobot/project-static/docs/release-notes/version-2.0.html +3 -3
  131. nautobot/project-static/docs/release-notes/version-2.1.html +3 -3
  132. nautobot/project-static/docs/release-notes/version-2.2.html +370 -99
  133. nautobot/project-static/docs/requirements.txt +2 -1
  134. nautobot/project-static/docs/search/search_index.json +1 -1
  135. nautobot/project-static/docs/sitemap.xml +256 -256
  136. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  137. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +3 -3
  138. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +3 -3
  139. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +3 -3
  140. nautobot/project-static/docs/user-guide/administration/configuration/index.html +3 -3
  141. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +3 -3
  142. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +3 -3
  143. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +3 -3
  144. nautobot/project-static/docs/user-guide/administration/guides/caching.html +3 -3
  145. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +3 -3
  146. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +3 -3
  147. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +3 -3
  148. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +3 -3
  149. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +3 -3
  150. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +3 -3
  151. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +3 -3
  152. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +3 -3
  153. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +3 -3
  154. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +7 -7
  155. nautobot/project-static/docs/user-guide/administration/installation/index.html +3 -3
  156. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +8 -4
  157. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +3 -3
  158. nautobot/project-static/docs/user-guide/administration/installation/services.html +3 -3
  159. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +3 -3
  160. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +3 -3
  161. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +3 -3
  162. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +3 -3
  163. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +3 -3
  164. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +3 -3
  165. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +3 -3
  166. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +3 -3
  167. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +3 -3
  168. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +3 -3
  169. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +3 -3
  170. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +3 -3
  171. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +3 -3
  172. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +3 -3
  173. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +3 -3
  174. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +3 -3
  175. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +3 -3
  176. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +3 -3
  177. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +3 -3
  178. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +3 -3
  179. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +3 -3
  180. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +3 -3
  181. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +3 -3
  182. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +3 -3
  183. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +3 -3
  184. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +3 -3
  185. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +3 -3
  186. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +3 -3
  187. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +3 -3
  188. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +3 -3
  189. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +3 -3
  190. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +3 -3
  191. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +3 -3
  192. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +3 -3
  193. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +3 -3
  194. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +3 -3
  195. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +3 -3
  196. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +3 -3
  197. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +3 -3
  198. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +3 -3
  199. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +3 -3
  200. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +3 -3
  201. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +3 -3
  202. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +3 -3
  203. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +3 -3
  204. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +3 -3
  205. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +3 -3
  206. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +3 -3
  207. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +3 -3
  208. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +3 -3
  209. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +3 -3
  210. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +3 -3
  211. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +3 -3
  212. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +3 -3
  213. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +3 -3
  214. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +3 -3
  215. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +3 -3
  216. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +3 -3
  217. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +3 -3
  218. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +3 -3
  219. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +3 -3
  220. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +3 -3
  221. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +3 -3
  222. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +3 -3
  223. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +3 -3
  224. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +3 -3
  225. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +3 -3
  226. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +3 -3
  227. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +3 -3
  228. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +3 -3
  229. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +3 -3
  230. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +3 -3
  231. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +3 -3
  232. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +3 -3
  233. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +3 -3
  234. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +3 -3
  235. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +3 -3
  236. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +3 -3
  237. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +3 -3
  238. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +3 -3
  239. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +3 -3
  240. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +3 -3
  241. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +3 -3
  242. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +3 -3
  243. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +3 -3
  244. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +3 -3
  245. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +3 -3
  246. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +3 -3
  247. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +3 -3
  248. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +3 -3
  249. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +3 -3
  250. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +3 -3
  251. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +3 -3
  252. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +3 -3
  253. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +3 -3
  254. nautobot/project-static/docs/user-guide/index.html +3 -3
  255. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +3 -3
  256. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +3 -3
  257. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +3 -3
  258. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +3 -3
  259. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +3 -3
  260. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +3 -3
  261. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +3 -3
  262. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +3 -3
  263. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +3 -3
  264. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +3 -3
  265. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +3 -3
  266. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +3 -3
  267. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +3 -3
  268. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +3 -3
  269. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +3 -3
  270. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +3 -3
  271. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +3 -3
  272. nautobot/project-static/docs/user-guide/platform-functionality/note.html +3 -3
  273. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +3 -3
  274. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +3 -3
  275. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +3 -3
  276. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +3 -3
  277. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +3 -3
  278. nautobot/project-static/docs/user-guide/platform-functionality/role.html +3 -3
  279. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +3 -3
  280. nautobot/project-static/docs/user-guide/platform-functionality/status.html +3 -3
  281. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +3 -3
  282. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +3 -3
  283. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +3 -3
  284. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +3 -3
  285. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +3 -3
  286. nautobot/project-static/js/connection_toggles.js +7 -6
  287. {nautobot-2.2.6.dist-info → nautobot-2.2.8.dist-info}/METADATA +4 -4
  288. {nautobot-2.2.6.dist-info → nautobot-2.2.8.dist-info}/RECORD +292 -291
  289. {nautobot-2.2.6.dist-info → nautobot-2.2.8.dist-info}/LICENSE.txt +0 -0
  290. {nautobot-2.2.6.dist-info → nautobot-2.2.8.dist-info}/NOTICE +0 -0
  291. {nautobot-2.2.6.dist-info → nautobot-2.2.8.dist-info}/WHEEL +0 -0
  292. {nautobot-2.2.6.dist-info → nautobot-2.2.8.dist-info}/entry_points.txt +0 -0
@@ -15,9 +15,11 @@ from nautobot.core.tables import RelationshipColumn
15
15
  from nautobot.core.testing import TestCase
16
16
  from nautobot.core.testing.models import ModelTestCases
17
17
  from nautobot.core.utils.lookup import get_route_for_model
18
+ from nautobot.dcim.forms import DeviceForm
18
19
  from nautobot.dcim.models import (
19
20
  Controller,
20
21
  Device,
22
+ DeviceType,
21
23
  DeviceTypeToSoftwareImageFile,
22
24
  Location,
23
25
  LocationType,
@@ -27,7 +29,7 @@ from nautobot.dcim.models import (
27
29
  from nautobot.dcim.tables import LocationTable
28
30
  from nautobot.dcim.tests.test_views import create_test_device
29
31
  from nautobot.extras.choices import RelationshipRequiredSideChoices, RelationshipSideChoices, RelationshipTypeChoices
30
- from nautobot.extras.models import Relationship, RelationshipAssociation, Status
32
+ from nautobot.extras.models import Relationship, RelationshipAssociation, Role, Status
31
33
  from nautobot.ipam.models import VLAN, VLANGroup
32
34
 
33
35
 
@@ -486,6 +488,224 @@ class RelationshipTest(RelationshipBaseTest, ModelTestCases.BaseModelTestCase):
486
488
  with self.assertNumQueries(0):
487
489
  manager_method(Location)
488
490
 
491
+ def test_required_related_object_errors(self):
492
+ """
493
+ Confirm that the fix in https://github.com/nautobot/nautobot/pull/5570 is working as expected
494
+ """
495
+ device_ct = ContentType.objects.get_for_model(Device)
496
+ status = Status.objects.get_for_model(Device).first()
497
+ device_type = DeviceType.objects.exclude(manufacturer__isnull=True).first()
498
+ # Create a Device with role Role 1
499
+ role_1 = Role.objects.create(name="Role 1")
500
+ role_1.content_types.add(ContentType.objects.get_for_model(Device))
501
+ device_1 = Device.objects.create(
502
+ device_type=device_type, role=role_1, name="Device 1", location=self.locations[0], status=status
503
+ )
504
+ # Create a Device with role Role 2
505
+ role_2 = Role.objects.create(name="Role 2")
506
+ role_2.content_types.add(ContentType.objects.get_for_model(Device))
507
+ device_2 = Device.objects.create(
508
+ device_type=device_type, role=role_2, name="Device 2", location=self.locations[0], status=status
509
+ )
510
+ # Create a Device with role Role 3
511
+ role_3 = Role.objects.create(name="Role 3")
512
+ role_3.content_types.add(ContentType.objects.get_for_model(Device))
513
+ device_3 = Device.objects.create(
514
+ device_type=device_type, role=role_3, name="Device 3", location=self.locations[0], status=status
515
+ )
516
+ # Create a one-to-many relationship with destination required, source filter: {"role": ["Role 1"]}
517
+ # and destination filter {"role": ["Role 2"]}
518
+ relationship = Relationship.objects.create(
519
+ label="Device to Devices",
520
+ key="device_to_devices",
521
+ source_type=device_ct,
522
+ source_filter={"role": ["Role 1"]},
523
+ destination_type=device_ct,
524
+ destination_filter={"role": ["Role 2"]},
525
+ type=RelationshipTypeChoices.TYPE_ONE_TO_MANY,
526
+ required_on="destination",
527
+ )
528
+ # Attempt to update device_3 which will not be in the queryset filtered by the destination filter
529
+ # Assert that the form is valid and no ValueError is raised.
530
+ update_status = Status.objects.get_for_model(Device).last()
531
+ update_data_for_device_3 = {
532
+ "location": device_3.location.pk,
533
+ "device_type": device_3.device_type.pk,
534
+ "role": device_3.role.pk,
535
+ "name": device_3.name,
536
+ "status": update_status.pk,
537
+ }
538
+ form = DeviceForm(instance=device_3, data=update_data_for_device_3)
539
+ self.assertTrue(form.is_valid())
540
+ # Attempt to update device_1 which will not be in the destination filter,
541
+ # but is in the source filter.
542
+ update_data_for_device_1 = {
543
+ "location": device_1.location.pk,
544
+ "device_type": device_1.device_type.pk,
545
+ "role": device_1.role.pk,
546
+ "name": device_1.name,
547
+ "status": update_status.pk,
548
+ }
549
+ form2 = DeviceForm(instance=device_1, data=update_data_for_device_1)
550
+ self.assertTrue(form2.is_valid())
551
+ # Attempt to update device_2 which will be in the destination filter, so it should
552
+ # require the relationship.
553
+ update_data_for_device_2 = {
554
+ "location": device_2.location.pk,
555
+ "device_type": device_2.device_type.pk,
556
+ "role": device_2.role.pk,
557
+ "name": "Device 2",
558
+ "status": update_status.pk,
559
+ }
560
+ form3 = DeviceForm(instance=device_2, data=update_data_for_device_2)
561
+ self.assertFalse(form3.is_valid())
562
+ # Device 1 has a relationship to Device 2
563
+ update_data_for_device_1 = {
564
+ "location": device_1.location.pk,
565
+ "device_type": device_1.device_type.pk,
566
+ "role": device_1.role.pk,
567
+ "name": device_1.name,
568
+ "status": update_status.pk,
569
+ "cr_device_to_devices__destination": [device_2.pk],
570
+ }
571
+ form4 = DeviceForm(instance=device_1, data=update_data_for_device_1)
572
+ self.assertTrue(form4.is_valid())
573
+ form4.save()
574
+ # Device 2 has a relationship to Device 1, form should validate and save.
575
+ update_data_for_device_2 = {
576
+ "location": device_2.location.pk,
577
+ "device_type": device_2.device_type.pk,
578
+ "role": device_2.role.pk,
579
+ "name": "Device 2",
580
+ "status": update_status.pk,
581
+ "cr_device_to_devices__source": device_1.pk,
582
+ }
583
+ form5 = DeviceForm(instance=device_2, data=update_data_for_device_2)
584
+ self.assertTrue(form5.is_valid())
585
+ form5.save()
586
+ # Device 2 has a relationship to Device 3, save should fail as Device 3 doesn't match filter.
587
+ update_data_for_device_2 = {
588
+ "location": device_2.location.pk,
589
+ "device_type": device_2.device_type.pk,
590
+ "role": device_2.role.pk,
591
+ "name": "Device 2",
592
+ "status": update_status.pk,
593
+ "cr_device_to_devices__source": device_3.pk,
594
+ }
595
+ form6 = DeviceForm(instance=device_2, data=update_data_for_device_2)
596
+ with self.assertRaises(ValidationError):
597
+ form6.save()
598
+ # Device 1 has a relationship to Device 3, save should fail as Device 3 doesn't match filter.
599
+ update_data_for_device_1 = {
600
+ "location": device_1.location.pk,
601
+ "device_type": device_1.device_type.pk,
602
+ "role": device_1.role.pk,
603
+ "name": "Device 1",
604
+ "status": update_status.pk,
605
+ "cr_device_to_devices__destination": [
606
+ device_3.pk,
607
+ ],
608
+ }
609
+ form6 = DeviceForm(instance=device_1, data=update_data_for_device_1)
610
+ with self.assertRaises(ValidationError):
611
+ form6.save()
612
+
613
+ relationship.required_on = "source"
614
+ relationship.save()
615
+ # Attempt to update device_3 which will not be in the queryset filtered by the destination filter
616
+ # Assert that the form is valid and no ValueError is raised. This ensures that an object that
617
+ # does not take part in any relationships can still be updated, addressing issue #5569.
618
+ update_status = Status.objects.get_for_model(Device).last()
619
+ update_data_for_device_3 = {
620
+ "location": device_3.location.pk,
621
+ "device_type": device_3.device_type.pk,
622
+ "role": device_3.role.pk,
623
+ "name": device_3.name,
624
+ "status": update_status.pk,
625
+ }
626
+ form = DeviceForm(instance=device_3, data=update_data_for_device_3)
627
+ self.assertTrue(form.is_valid())
628
+ # Attempt to update device_1 which will not be in the destination filter,
629
+ # but is in the source filter. Should fail as device_to_devices is required.
630
+ device_1.delete()
631
+ device_1 = Device.objects.create(
632
+ device_type=device_type, role=role_1, name="Device 1", location=self.locations[0], status=status
633
+ )
634
+ update_data_for_device_1 = {
635
+ "location": device_1.location.pk,
636
+ "device_type": device_1.device_type.pk,
637
+ "role": device_1.role.pk,
638
+ "name": device_1.name,
639
+ "status": update_status.pk,
640
+ }
641
+ form2 = DeviceForm(instance=device_1, data=update_data_for_device_1)
642
+ self.assertFalse(form2.is_valid())
643
+ # Attempt to update device_2 which will be in the destination filter, which should not require the
644
+ # relationship anymore.
645
+ device_2.delete()
646
+ device_2 = Device.objects.create(
647
+ device_type=device_type, role=role_2, name="Device 2", location=self.locations[0], status=status
648
+ )
649
+ update_data_for_device_2 = {
650
+ "location": device_2.location.pk,
651
+ "device_type": device_2.device_type.pk,
652
+ "role": device_2.role.pk,
653
+ "name": "Device 2",
654
+ "status": update_status.pk,
655
+ }
656
+ form3 = DeviceForm(instance=device_2, data=update_data_for_device_2)
657
+ self.assertTrue(form3.is_valid())
658
+ # Device 1 has a relationship to Device 2
659
+ update_data_for_device_1 = {
660
+ "location": device_1.location.pk,
661
+ "device_type": device_1.device_type.pk,
662
+ "role": device_1.role.pk,
663
+ "name": device_1.name,
664
+ "status": update_status.pk,
665
+ "cr_device_to_devices__destination": [device_2.pk],
666
+ }
667
+ form4 = DeviceForm(instance=device_1, data=update_data_for_device_1)
668
+ self.assertTrue(form4.is_valid())
669
+ form4.save()
670
+ # Device 2 has a relationship to Device 1, form should validate and save.
671
+ update_data_for_device_2 = {
672
+ "location": device_2.location.pk,
673
+ "device_type": device_2.device_type.pk,
674
+ "role": device_2.role.pk,
675
+ "name": "Device 2",
676
+ "status": update_status.pk,
677
+ "cr_device_to_devices__source": device_1.pk,
678
+ }
679
+ form5 = DeviceForm(instance=device_2, data=update_data_for_device_2)
680
+ self.assertTrue(form5.is_valid())
681
+ form5.save()
682
+ # Device 2 has a relationship to Device 3, save should fail as Device 3 doesn't match filter.
683
+ update_data_for_device_2 = {
684
+ "location": device_2.location.pk,
685
+ "device_type": device_2.device_type.pk,
686
+ "role": device_2.role.pk,
687
+ "name": "Device 2",
688
+ "status": update_status.pk,
689
+ "cr_device_to_devices__source": device_3.pk,
690
+ }
691
+ form6 = DeviceForm(instance=device_2, data=update_data_for_device_2)
692
+ with self.assertRaises(ValidationError):
693
+ form6.save()
694
+ # Device 1 has a relationship to Device 3, save should fail as Device 3 doesn't match filter.
695
+ update_data_for_device_1 = {
696
+ "location": device_1.location.pk,
697
+ "device_type": device_1.device_type.pk,
698
+ "role": device_1.role.pk,
699
+ "name": "Device 1",
700
+ "status": update_status.pk,
701
+ "cr_device_to_devices__destination": [
702
+ device_3.pk,
703
+ ],
704
+ }
705
+ form6 = DeviceForm(instance=device_1, data=update_data_for_device_1)
706
+ with self.assertRaises(ValidationError):
707
+ form6.save()
708
+
489
709
 
490
710
  class RelationshipAssociationTest(RelationshipBaseTest, ModelTestCases.BaseModelTestCase):
491
711
  model = RelationshipAssociation
nautobot/extras/views.py CHANGED
@@ -1069,7 +1069,7 @@ def check_and_call_git_repository_function(request, pk, func):
1069
1069
  # Allow execution only if a worker process is running.
1070
1070
  if not get_worker_count():
1071
1071
  messages.error(request, "Unable to run job: Celery worker process not running.")
1072
- return redirect(request.get_full_path(), permanent=False)
1072
+ return redirect(reverse("extras:gitrepository", args=(pk,)), permanent=False)
1073
1073
  else:
1074
1074
  repository = get_object_or_404(GitRepository.objects.restrict(request.user, "change"), pk=pk)
1075
1075
  job_result = func(repository, request.user)
@@ -321,27 +321,30 @@ class PrefixLocationAssignmentSerializer(ValidatedModelSerializer):
321
321
  fields = "__all__"
322
322
 
323
323
 
324
- class PrefixLengthSerializer(serializers.Serializer):
325
- prefix_length = serializers.IntegerField()
326
-
327
- def to_internal_value(self, data):
328
- requested_prefix = data.get("prefix_length")
329
- if requested_prefix is None:
330
- raise serializers.ValidationError({"prefix_length": "this field can not be missing"})
331
- if not isinstance(requested_prefix, int):
332
- raise serializers.ValidationError({"prefix_length": "this field must be int type"})
333
-
334
- prefix = self.context.get("prefix")
335
- if prefix.ip_version == 4 and requested_prefix > 32:
336
- raise serializers.ValidationError({"prefix_length": f"Invalid prefix length ({requested_prefix}) for IPv4"})
337
- elif prefix.ip_version == 6 and requested_prefix > 128:
338
- raise serializers.ValidationError({"prefix_length": f"Invalid prefix length ({requested_prefix}) for IPv6"})
339
- return data
324
+ class PrefixLengthSerializer(PrefixLegacySerializer):
325
+ """
326
+ Input serializer for POST to /api/ipam/prefixes/<id>/available-prefixes/, i.e. allocating one or more sub-prefixes.
327
+
328
+ Since setting of multiple locations on create is not supported, this uses the legacy single-location option.
329
+ """
330
+
331
+ prefix_length = serializers.IntegerField(required=True)
332
+
333
+ class Meta(PrefixLegacySerializer.Meta):
334
+ fields = PrefixLegacySerializer.Meta.fields.copy()
335
+ fields.remove("prefix")
336
+ fields.remove("network")
337
+ fields.remove("broadcast")
338
+ fields.remove("parent")
339
+ fields.remove("ip_version")
340
+ fields.remove("namespace")
340
341
 
341
342
 
342
343
  class AvailablePrefixSerializer(serializers.Serializer):
343
344
  """
344
345
  Representation of a prefix which does not exist in the database.
346
+
347
+ Response serializer for a GET to /api/ipam/prefixes/<id>/available-prefixes/.
345
348
  """
346
349
 
347
350
  ip_version = serializers.IntegerField(read_only=True)
@@ -437,6 +440,8 @@ class IPAddressSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
437
440
  class AvailableIPSerializer(serializers.Serializer):
438
441
  """
439
442
  Representation of an IP address which does not exist in the database.
443
+
444
+ Response serializer for a GET to /api/ipam/prefixes/<id>/available-ips/.
440
445
  """
441
446
 
442
447
  ip_version = serializers.IntegerField(read_only=True)
@@ -451,6 +456,31 @@ class AvailableIPSerializer(serializers.Serializer):
451
456
  )
452
457
 
453
458
 
459
+ class IPAllocationSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
460
+ """
461
+ Input serializer for POST to /api/ipam/prefixes/<id>/available-ips/, i.e. allocating addresses from a prefix.
462
+ """
463
+
464
+ class Meta:
465
+ model = IPAddress
466
+ fields = (
467
+ # not address/namespace/parent as those are implied by the selected prefix
468
+ "status",
469
+ "type",
470
+ "dns_name",
471
+ "description",
472
+ "role",
473
+ "tenant",
474
+ "nat_inside",
475
+ "tags",
476
+ "custom_fields",
477
+ )
478
+
479
+ def validate(self, data):
480
+ data["mask_length"] = self.context["prefix"].prefix_length
481
+ return super().validate(data)
482
+
483
+
454
484
  #
455
485
  # IP address to interface
456
486
  #
@@ -121,8 +121,6 @@ class PrefixViewSet(NautobotModelViewSet):
121
121
  filterset_class = filters.PrefixFilterSet
122
122
 
123
123
  def get_serializer_class(self):
124
- if self.action == "available_prefixes" and self.request.method == "POST":
125
- return serializers.PrefixLengthSerializer
126
124
  if (
127
125
  not getattr(self, "swagger_fake_view", False)
128
126
  and self.request.major_version == 2
@@ -159,7 +157,11 @@ class PrefixViewSet(NautobotModelViewSet):
159
157
  raise self.LocationIncompatibleLegacyBehavior from e
160
158
 
161
159
  @extend_schema(methods=["get"], responses={200: serializers.AvailablePrefixSerializer(many=True)})
162
- @extend_schema(methods=["post"], responses={201: serializers.PrefixSerializer(many=False)})
160
+ @extend_schema(
161
+ methods=["post"],
162
+ request=serializers.PrefixLengthSerializer,
163
+ responses={201: serializers.PrefixSerializer(many=True)},
164
+ )
163
165
  @action(
164
166
  detail=True,
165
167
  name="Available Prefixes",
@@ -169,10 +171,10 @@ class PrefixViewSet(NautobotModelViewSet):
169
171
  )
170
172
  def available_prefixes(self, request, pk=None):
171
173
  """
172
- A convenience method for returning available child prefixes within a parent.
174
+ A convenience method for listing and/or allocating available child prefixes within a parent.
173
175
 
174
- The advisory lock decorator uses a PostgreSQL advisory lock to prevent this API from being
175
- invoked in parallel, which results in a race condition where multiple insertions can occur.
176
+ This uses a Redis lock to prevent this API from being invoked in parallel, in order to avoid a race condition
177
+ if multiple clients tried to simultaneously request allocation from the same parent prefix.
176
178
  """
177
179
  prefix = get_object_or_404(self.queryset, pk=pk)
178
180
  if request.method == "POST":
@@ -214,9 +216,9 @@ class PrefixViewSet(NautobotModelViewSet):
214
216
  # Initialize the serializer with a list or a single object depending on what was requested
215
217
  context = {"request": request, "depth": 0}
216
218
  if isinstance(request.data, list):
217
- serializer = serializers.PrefixSerializer(data=requested_prefixes, many=True, context=context)
219
+ serializer = self.get_serializer_class()(data=requested_prefixes, many=True, context=context)
218
220
  else:
219
- serializer = serializers.PrefixSerializer(data=requested_prefixes[0], context=context)
221
+ serializer = self.get_serializer_class()(data=requested_prefixes[0], context=context)
220
222
 
221
223
  # Create the new Prefix(es)
222
224
  serializer.is_valid(raise_exception=True)
@@ -238,8 +240,8 @@ class PrefixViewSet(NautobotModelViewSet):
238
240
  @extend_schema(methods=["get"], responses={200: serializers.AvailableIPSerializer(many=True)})
239
241
  @extend_schema(
240
242
  methods=["post"],
241
- responses={201: serializers.AvailableIPSerializer(many=True)},
242
- request=serializers.AvailableIPSerializer(many=True),
243
+ responses={201: serializers.IPAddressSerializer(many=True)},
244
+ request=serializers.IPAllocationSerializer(many=True),
243
245
  )
244
246
  @action(
245
247
  detail=True,
@@ -251,12 +253,13 @@ class PrefixViewSet(NautobotModelViewSet):
251
253
  )
252
254
  def available_ips(self, request, pk=None):
253
255
  """
254
- A convenience method for returning available IP addresses within a prefix. By default, the number of IPs
255
- returned will be equivalent to PAGINATE_COUNT. An arbitrary limit (up to MAX_PAGE_SIZE, if set) may be passed,
256
- however results will not be paginated.
256
+ A convenience method for listing and/or allocating available IP addresses within a prefix.
257
+
258
+ By default, the number of IPs returned will be equivalent to PAGINATE_COUNT.
259
+ An arbitrary limit (up to MAX_PAGE_SIZE, if set) may be passed, however results will not be paginated.
257
260
 
258
- The advisory lock decorator uses a PostgreSQL advisory lock to prevent this API from being
259
- invoked in parallel, which results in a race condition where multiple insertions can occur.
261
+ This uses a Redis lock to prevent this API from being invoked in parallel, in order to avoid a race condition
262
+ if multiple clients tried to simultaneously request allocation from the same parent prefix.
260
263
  """
261
264
  prefix = get_object_or_404(Prefix.objects.restrict(request.user), pk=pk)
262
265
 
@@ -266,7 +269,17 @@ class PrefixViewSet(NautobotModelViewSet):
266
269
  "nautobot.ipam.api.views.available_ips", blocking_timeout=5, timeout=settings.REDIS_LOCK_TIMEOUT
267
270
  ):
268
271
  # Normalize to a list of objects
269
- requested_ips = request.data if isinstance(request.data, list) else [request.data]
272
+ serializer = serializers.IPAllocationSerializer(
273
+ data=request.data if isinstance(request.data, list) else [request.data],
274
+ many=True,
275
+ context={
276
+ "request": request,
277
+ "prefix": prefix,
278
+ },
279
+ )
280
+ serializer.is_valid(raise_exception=True)
281
+
282
+ requested_ips = serializer.validated_data
270
283
 
271
284
  # Determine if the requested number of IPs is available
272
285
  available_ips = prefix.get_available_ips()
@@ -454,7 +454,6 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
454
454
  for i in range(4):
455
455
  data = {
456
456
  "prefix_length": child_prefix_length,
457
- "namespace": self.namespace.pk,
458
457
  "status": self.status.pk,
459
458
  "description": f"Test Prefix {i + 1}",
460
459
  }
@@ -464,13 +463,18 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
464
463
  self.assertEqual(str(response.data["namespace"]["url"]), self.absolute_api_url(prefix.namespace))
465
464
  self.assertEqual(response.data["description"], data["description"])
466
465
 
467
- # Try to create one more prefix
468
- response = self.client.post(url, {"prefix_length": child_prefix_length}, format="json", **self.header)
466
+ # Try to create one more prefix, and expect a HTTP 204 response.
467
+ # This feels wrong to me (shouldn't it be a 4xx or 5xx?) but it's how the API has historically behaved.
468
+ response = self.client.post(
469
+ url, {"prefix_length": child_prefix_length, "status": self.status.pk}, format="json", **self.header
470
+ )
469
471
  self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
470
472
  self.assertIn("detail", response.data)
471
473
 
472
- # Try to create invalid prefix type
473
- response = self.client.post(url, {"prefix_length": str(child_prefix_length)}, format="json", **self.header)
474
+ # Invalid data does trigger a HTTP 400 response.
475
+ response = self.client.post(
476
+ url, {"prefix_length": "hello", "status": self.status.pk}, format="json", **self.header
477
+ )
474
478
  self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
475
479
  self.assertIn("prefix_length", response.data[0])
476
480
 
@@ -495,35 +499,31 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
495
499
  {
496
500
  "prefix_length": child_prefix_length,
497
501
  "description": "Test Prefix 1",
498
- "namespace": self.namespace.pk,
499
502
  "status": self.status.pk,
500
503
  },
501
504
  {
502
505
  "prefix_length": child_prefix_length,
503
506
  "description": "Test Prefix 2",
504
- "namespace": self.namespace.pk,
505
507
  "status": self.status.pk,
506
508
  },
507
509
  {
508
510
  "prefix_length": child_prefix_length,
509
511
  "description": "Test Prefix 3",
510
- "namespace": self.namespace.pk,
511
512
  "status": self.status.pk,
512
513
  },
513
514
  {
514
515
  "prefix_length": child_prefix_length,
515
516
  "description": "Test Prefix 4",
516
- "namespace": self.namespace.pk,
517
517
  "status": self.status.pk,
518
518
  },
519
519
  {
520
520
  "prefix_length": child_prefix_length,
521
521
  "description": "Test Prefix 5",
522
- "namespace": self.namespace.pk,
523
522
  "status": self.status.pk,
524
523
  },
525
524
  ]
526
525
  response = self.client.post(url, data, format="json", **self.header)
526
+ # This feels wrong to me (shouldn't it be a 4xx or 5xx?) but it's how the API has historically behaved.
527
527
  self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
528
528
  self.assertIn("detail", response.data)
529
529
 
@@ -577,7 +577,6 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
577
577
  for i in range(1, 7):
578
578
  data = {
579
579
  "description": f"Test IP {i}",
580
- "namespace": self.namespace.pk,
581
580
  "status": self.status.pk,
582
581
  }
583
582
  response = self.client.post(url, data, format="json", **self.header)
@@ -586,7 +585,8 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
586
585
  self.assertEqual(response.data["description"], data["description"])
587
586
 
588
587
  # Try to create one more IP
589
- response = self.client.post(url, {}, format="json", **self.header)
588
+ response = self.client.post(url, {"status": self.status.pk}, format="json", **self.header)
589
+ # This feels wrong to me (shouldn't it be a 4xx or 5xx?) but it's how the API has historically behaved.
590
590
  self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
591
591
  self.assertIn("detail", response.data)
592
592
 
@@ -604,19 +604,14 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
604
604
  self.add_permissions("ipam.view_prefix", "ipam.add_ipaddress", "extras.view_status")
605
605
 
606
606
  # Try to create seven IPs (only six are available)
607
- data = [
608
- {"description": f"Test IP {i}", "namespace": self.namespace.pk, "status": self.status.pk}
609
- for i in range(1, 8)
610
- ] # 7 IPs
607
+ data = [{"description": f"Test IP {i}", "status": self.status.pk} for i in range(1, 8)] # 7 IPs
611
608
  response = self.client.post(url, data, format="json", **self.header)
609
+ # This feels wrong to me (shouldn't it be a 4xx or 5xx?) but it's how the API has historically behaved.
612
610
  self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
613
611
  self.assertIn("detail", response.data)
614
612
 
615
613
  # Create all six available IPs in a single request
616
- data = [
617
- {"description": f"Test IP {i}", "namespace": self.namespace.pk, "status": self.status.pk}
618
- for i in range(1, 7)
619
- ] # 6 IPs
614
+ data = [{"description": f"Test IP {i}", "status": self.status.pk} for i in range(1, 7)] # 6 IPs
620
615
  response = self.client.post(url, data, format="json", **self.header)
621
616
  self.assertHttpStatus(response, status.HTTP_201_CREATED)
622
617
  self.assertEqual(len(response.data), 6)
@@ -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.0, mkdocs-material-9.5.26">
15
+ <meta name="generator" content="mkdocs-1.6.0, mkdocs-material-9.5.29">
16
16
 
17
17
 
18
18
 
@@ -20,7 +20,7 @@
20
20
 
21
21
 
22
22
 
23
- <link rel="stylesheet" href="/projects/core/en/stable/assets/stylesheets/main.6543a935.min.css">
23
+ <link rel="stylesheet" href="/projects/core/en/stable/assets/stylesheets/main.76a95c52.min.css">
24
24
 
25
25
 
26
26
  <link rel="stylesheet" href="/projects/core/en/stable/assets/stylesheets/palette.06af60db.min.css">
@@ -7913,7 +7913,7 @@
7913
7913
  <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.b8dbb3d2.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>
7914
7914
 
7915
7915
 
7916
- <script src="/projects/core/en/stable/assets/javascripts/bundle.ad660dcc.min.js"></script>
7916
+ <script src="/projects/core/en/stable/assets/javascripts/bundle.fe8b6f2b.min.js"></script>
7917
7917
 
7918
7918
  <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
7919
7919
 
@@ -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.0, mkdocs-material-9.5.26">
21
+ <meta name="generator" content="mkdocs-1.6.0, mkdocs-material-9.5.29">
22
22
 
23
23
 
24
24
 
@@ -26,7 +26,7 @@
26
26
 
27
27
 
28
28
 
29
- <link rel="stylesheet" href="../assets/stylesheets/main.6543a935.min.css">
29
+ <link rel="stylesheet" href="../assets/stylesheets/main.76a95c52.min.css">
30
30
 
31
31
 
32
32
  <link rel="stylesheet" href="../assets/stylesheets/palette.06af60db.min.css">
@@ -8080,7 +8080,7 @@
8080
8080
  <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.b8dbb3d2.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>
8081
8081
 
8082
8082
 
8083
- <script src="../assets/javascripts/bundle.ad660dcc.min.js"></script>
8083
+ <script src="../assets/javascripts/bundle.fe8b6f2b.min.js"></script>
8084
8084
 
8085
8085
  <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
8086
8086
 
@@ -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.0, mkdocs-material-9.5.26">
19
+ <meta name="generator" content="mkdocs-1.6.0, mkdocs-material-9.5.29">
20
20
 
21
21
 
22
22
 
@@ -24,7 +24,7 @@
24
24
 
25
25
 
26
26
 
27
- <link rel="stylesheet" href="../assets/stylesheets/main.6543a935.min.css">
27
+ <link rel="stylesheet" href="../assets/stylesheets/main.76a95c52.min.css">
28
28
 
29
29
 
30
30
  <link rel="stylesheet" href="../assets/stylesheets/palette.06af60db.min.css">
@@ -8014,7 +8014,7 @@
8014
8014
  <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.b8dbb3d2.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>
8015
8015
 
8016
8016
 
8017
- <script src="../assets/javascripts/bundle.ad660dcc.min.js"></script>
8017
+ <script src="../assets/javascripts/bundle.fe8b6f2b.min.js"></script>
8018
8018
 
8019
8019
  <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
8020
8020