nautobot 2.4.5__py3-none-any.whl → 2.4.7__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 (410) hide show
  1. nautobot/apps/forms.py +2 -0
  2. nautobot/circuits/templates/circuits/providernetwork.html +1 -1
  3. nautobot/circuits/templates/circuits/providernetwork_retrieve.html +2 -55
  4. nautobot/circuits/views.py +20 -23
  5. nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +2 -40
  6. nautobot/cloud/views.py +12 -0
  7. nautobot/core/api/mixins.py +10 -0
  8. nautobot/core/api/urls.py +2 -2
  9. nautobot/core/api/views.py +39 -1
  10. nautobot/core/celery/encoders.py +2 -2
  11. nautobot/core/forms/__init__.py +2 -0
  12. nautobot/core/forms/fields.py +23 -7
  13. nautobot/core/forms/utils.py +1 -0
  14. nautobot/core/forms/widgets.py +18 -0
  15. nautobot/core/jobs/bulk_actions.py +1 -1
  16. nautobot/core/management/commands/generate_test_data.py +1 -1
  17. nautobot/core/models/name_color_content_types.py +9 -0
  18. nautobot/core/models/validators.py +7 -0
  19. nautobot/core/settings.py +0 -14
  20. nautobot/core/settings.yaml +0 -28
  21. nautobot/core/tables.py +6 -1
  22. nautobot/core/templates/generic/object_retrieve.html +1 -1
  23. nautobot/core/templates/widgets/sluginput.html +5 -1
  24. nautobot/core/templatetags/helpers.py +15 -1
  25. nautobot/core/testing/api.py +18 -0
  26. nautobot/core/testing/integration.py +6 -2
  27. nautobot/core/tests/nautobot_config.py +0 -2
  28. nautobot/core/tests/runner.py +17 -140
  29. nautobot/core/tests/test_api.py +4 -4
  30. nautobot/core/tests/test_authentication.py +83 -4
  31. nautobot/core/tests/test_forms.py +11 -8
  32. nautobot/core/tests/test_graphql.py +9 -0
  33. nautobot/core/tests/test_jobs.py +7 -0
  34. nautobot/core/ui/object_detail.py +47 -3
  35. nautobot/core/utils/lookup.py +2 -2
  36. nautobot/dcim/factory.py +2 -0
  37. nautobot/dcim/filters/__init__.py +5 -0
  38. nautobot/dcim/forms.py +27 -1
  39. nautobot/dcim/migrations/0068_alter_softwareimagefile_download_url.py +19 -0
  40. nautobot/dcim/migrations/0069_softwareimagefile_external_integration.py +25 -0
  41. nautobot/dcim/models/devices.py +9 -2
  42. nautobot/dcim/models/locations.py +9 -0
  43. nautobot/dcim/tables/devices.py +1 -0
  44. nautobot/dcim/templates/dcim/device_list.html +1 -1
  45. nautobot/dcim/templates/dcim/devicetype.html +1 -1
  46. nautobot/dcim/templates/dcim/manufacturer.html +1 -63
  47. nautobot/dcim/templates/dcim/module_list.html +1 -1
  48. nautobot/dcim/templates/dcim/moduletype_retrieve.html +1 -1
  49. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +4 -0
  50. nautobot/dcim/tests/integration/test_module_bay_position.py +125 -0
  51. nautobot/dcim/tests/test_api.py +74 -31
  52. nautobot/dcim/tests/test_filters.py +2 -0
  53. nautobot/dcim/tests/test_models.py +78 -0
  54. nautobot/dcim/tests/test_views.py +7 -1
  55. nautobot/dcim/urls.py +1 -45
  56. nautobot/dcim/views.py +35 -66
  57. nautobot/extras/choices.py +4 -0
  58. nautobot/extras/filters/__init__.py +6 -0
  59. nautobot/extras/forms/forms.py +83 -11
  60. nautobot/extras/models/customfields.py +10 -9
  61. nautobot/extras/plugins/marketplace_manifest.yml +18 -0
  62. nautobot/extras/signals.py +43 -4
  63. nautobot/extras/tables.py +4 -5
  64. nautobot/extras/tasks.py +4 -2
  65. nautobot/extras/templates/extras/contact_retrieve.html +1 -58
  66. nautobot/extras/templates/extras/exporttemplate.html +1 -53
  67. nautobot/extras/templates/extras/inc/panel_changelog.html +1 -1
  68. nautobot/extras/templates/extras/inc/panel_jobhistory.html +1 -1
  69. nautobot/extras/templates/extras/status.html +1 -37
  70. nautobot/extras/templates/extras/team_retrieve.html +1 -58
  71. nautobot/extras/tests/integration/test_notes.py +1 -1
  72. nautobot/extras/tests/test_api.py +22 -7
  73. nautobot/extras/tests/test_changelog.py +4 -4
  74. nautobot/extras/tests/test_customfields.py +27 -0
  75. nautobot/extras/tests/test_plugins.py +19 -13
  76. nautobot/extras/tests/test_relationships.py +9 -0
  77. nautobot/extras/tests/test_tags.py +2 -2
  78. nautobot/extras/tests/test_views.py +37 -6
  79. nautobot/extras/urls.py +3 -100
  80. nautobot/extras/views.py +111 -133
  81. nautobot/ipam/tables.py +7 -2
  82. nautobot/ipam/templates/ipam/namespace_retrieve.html +0 -41
  83. nautobot/ipam/templates/ipam/service.html +2 -46
  84. nautobot/ipam/templates/ipam/service_edit.html +1 -17
  85. nautobot/ipam/templates/ipam/service_retrieve.html +7 -0
  86. nautobot/ipam/tests/migration/__init__.py +0 -0
  87. nautobot/ipam/tests/migration/test_migrations.py +510 -0
  88. nautobot/ipam/tests/test_api.py +66 -36
  89. nautobot/ipam/tests/test_filters.py +0 -10
  90. nautobot/ipam/tests/test_views.py +44 -2
  91. nautobot/ipam/urls.py +2 -47
  92. nautobot/ipam/utils/migrations.py +185 -152
  93. nautobot/ipam/utils/testing.py +177 -0
  94. nautobot/ipam/views.py +95 -157
  95. nautobot/project-static/css/base.css +5 -0
  96. nautobot/project-static/docs/404.html +0 -2
  97. nautobot/project-static/docs/apps/index.html +0 -2
  98. nautobot/project-static/docs/apps/nautobot-apps.html +0 -2
  99. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +0 -2
  100. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +0 -2
  101. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +0 -2
  102. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +0 -2
  103. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +0 -2
  104. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +0 -2
  105. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +0 -2
  106. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +0 -2
  107. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +0 -2
  108. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +0 -2
  109. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +0 -2
  110. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +0 -2
  111. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +62 -2
  112. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +0 -2
  113. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +0 -2
  114. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +47 -2
  115. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +0 -2
  116. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +0 -2
  117. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +18 -2
  118. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +0 -2
  119. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +70 -5
  120. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +0 -2
  121. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +0 -2
  122. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +0 -2
  123. nautobot/project-static/docs/development/apps/api/configuration-view.html +0 -2
  124. nautobot/project-static/docs/development/apps/api/database-backend-config.html +0 -2
  125. nautobot/project-static/docs/development/apps/api/models/django-admin.html +0 -2
  126. nautobot/project-static/docs/development/apps/api/models/global-search.html +0 -2
  127. nautobot/project-static/docs/development/apps/api/models/graphql.html +0 -2
  128. nautobot/project-static/docs/development/apps/api/models/index.html +0 -2
  129. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +0 -2
  130. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +0 -2
  131. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +0 -2
  132. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +0 -2
  133. nautobot/project-static/docs/development/apps/api/platform-features/index.html +0 -2
  134. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +0 -2
  135. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +0 -2
  136. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +0 -2
  137. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +0 -2
  138. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +0 -2
  139. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +0 -2
  140. nautobot/project-static/docs/development/apps/api/prometheus.html +0 -2
  141. nautobot/project-static/docs/development/apps/api/setup.html +0 -2
  142. nautobot/project-static/docs/development/apps/api/testing.html +0 -89
  143. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +0 -2
  144. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +0 -2
  145. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +0 -2
  146. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +0 -2
  147. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +0 -2
  148. nautobot/project-static/docs/development/apps/api/views/base-template.html +0 -2
  149. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +0 -2
  150. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +0 -2
  151. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +0 -2
  152. nautobot/project-static/docs/development/apps/api/views/index.html +0 -2
  153. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +0 -2
  154. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +0 -2
  155. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +0 -2
  156. nautobot/project-static/docs/development/apps/api/views/notes.html +0 -2
  157. nautobot/project-static/docs/development/apps/api/views/rest-api.html +0 -2
  158. nautobot/project-static/docs/development/apps/api/views/urls.html +0 -2
  159. nautobot/project-static/docs/development/apps/index.html +0 -2
  160. nautobot/project-static/docs/development/apps/migration/code-updates.html +0 -2
  161. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +1 -3
  162. nautobot/project-static/docs/development/apps/migration/from-v1.html +0 -2
  163. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +0 -2
  164. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +0 -2
  165. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +0 -2
  166. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +0 -2
  167. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +0 -2
  168. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +0 -2
  169. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +0 -2
  170. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +0 -2
  171. nautobot/project-static/docs/development/apps/porting-from-netbox.html +0 -2
  172. nautobot/project-static/docs/development/core/application-registry.html +0 -2
  173. nautobot/project-static/docs/development/core/best-practices.html +3 -5
  174. nautobot/project-static/docs/development/core/bootstrap-ui.html +0 -2
  175. nautobot/project-static/docs/development/core/caching.html +0 -2
  176. nautobot/project-static/docs/development/core/controllers.html +0 -2
  177. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +0 -2
  178. nautobot/project-static/docs/development/core/generic-views.html +0 -2
  179. nautobot/project-static/docs/development/core/getting-started.html +78 -109
  180. nautobot/project-static/docs/development/core/homepage.html +0 -2
  181. nautobot/project-static/docs/development/core/index.html +0 -2
  182. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +0 -2
  183. nautobot/project-static/docs/development/core/model-checklist.html +0 -2
  184. nautobot/project-static/docs/development/core/model-features.html +0 -2
  185. nautobot/project-static/docs/development/core/natural-keys.html +0 -2
  186. nautobot/project-static/docs/development/core/navigation-menu.html +0 -2
  187. nautobot/project-static/docs/development/core/release-checklist.html +1 -3
  188. nautobot/project-static/docs/development/core/role-internals.html +0 -2
  189. nautobot/project-static/docs/development/core/settings.html +0 -2
  190. nautobot/project-static/docs/development/core/style-guide.html +1 -3
  191. nautobot/project-static/docs/development/core/templates.html +0 -2
  192. nautobot/project-static/docs/development/core/testing.html +24 -200
  193. nautobot/project-static/docs/development/core/ui-component-framework.html +0 -2
  194. nautobot/project-static/docs/development/core/user-preferences.html +0 -2
  195. nautobot/project-static/docs/development/index.html +0 -2
  196. nautobot/project-static/docs/development/jobs/index.html +0 -2
  197. nautobot/project-static/docs/development/jobs/migration/from-v1.html +0 -2
  198. nautobot/project-static/docs/index.html +0 -2
  199. nautobot/project-static/docs/media/user-guide/administration/getting-started/nautobot-cloud.png +0 -0
  200. nautobot/project-static/docs/objects.inv +0 -0
  201. nautobot/project-static/docs/overview/application_stack.html +1 -3
  202. nautobot/project-static/docs/overview/design_philosophy.html +0 -2
  203. nautobot/project-static/docs/release-notes/index.html +0 -2
  204. nautobot/project-static/docs/release-notes/version-1.0.html +0 -2
  205. nautobot/project-static/docs/release-notes/version-1.1.html +0 -2
  206. nautobot/project-static/docs/release-notes/version-1.2.html +0 -2
  207. nautobot/project-static/docs/release-notes/version-1.3.html +0 -2
  208. nautobot/project-static/docs/release-notes/version-1.4.html +0 -2
  209. nautobot/project-static/docs/release-notes/version-1.5.html +0 -2
  210. nautobot/project-static/docs/release-notes/version-1.6.html +0 -2
  211. nautobot/project-static/docs/release-notes/version-2.0.html +0 -2
  212. nautobot/project-static/docs/release-notes/version-2.1.html +0 -2
  213. nautobot/project-static/docs/release-notes/version-2.2.html +0 -2
  214. nautobot/project-static/docs/release-notes/version-2.3.html +0 -2
  215. nautobot/project-static/docs/release-notes/version-2.4.html +364 -3
  216. nautobot/project-static/docs/search/search_index.json +1 -1
  217. nautobot/project-static/docs/sitemap.xml +290 -290
  218. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  219. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +0 -2
  220. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +0 -2
  221. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +0 -2
  222. nautobot/project-static/docs/user-guide/administration/configuration/index.html +0 -2
  223. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +0 -2
  224. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +2 -50
  225. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +0 -2
  226. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +0 -2
  227. nautobot/project-static/docs/user-guide/administration/guides/docker.html +0 -2
  228. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +0 -2
  229. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +71 -2
  230. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +0 -2
  231. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +0 -2
  232. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +0 -2
  233. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +0 -2
  234. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +0 -2
  235. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +0 -2
  236. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +0 -2
  237. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +3 -3
  238. nautobot/project-static/docs/user-guide/administration/installation/index.html +257 -18
  239. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +0 -2
  240. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +0 -2
  241. nautobot/project-static/docs/user-guide/administration/installation/services.html +0 -2
  242. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +0 -2
  243. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +0 -2
  244. nautobot/project-static/docs/user-guide/administration/security/index.html +0 -2
  245. nautobot/project-static/docs/user-guide/administration/security/notices.html +0 -2
  246. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +1 -3
  247. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +0 -2
  248. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +0 -2
  249. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +0 -2
  250. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +0 -2
  251. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +0 -2
  252. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +0 -2
  253. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +0 -2
  254. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +0 -2
  255. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +0 -2
  256. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +2 -4
  257. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +0 -2
  258. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +0 -2
  259. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +0 -2
  260. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +0 -2
  261. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +0 -2
  262. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +0 -2
  263. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +0 -2
  264. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +0 -2
  265. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +0 -2
  266. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +0 -2
  267. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +0 -2
  268. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +0 -2
  269. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +0 -2
  270. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +0 -2
  271. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +0 -2
  272. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +0 -2
  273. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +0 -2
  274. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +0 -2
  275. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +0 -2
  276. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +0 -2
  277. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +0 -2
  278. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +0 -2
  279. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +0 -2
  280. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +0 -2
  281. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +0 -2
  282. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +0 -2
  283. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +0 -2
  284. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +0 -2
  285. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +0 -2
  286. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +0 -2
  287. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +0 -2
  288. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +0 -2
  289. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +0 -2
  290. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +0 -2
  291. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +0 -2
  292. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +0 -2
  293. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +0 -2
  294. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +0 -2
  295. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +0 -2
  296. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +0 -2
  297. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +0 -2
  298. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +0 -2
  299. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +0 -2
  300. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +0 -2
  301. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +0 -2
  302. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +0 -2
  303. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +0 -2
  304. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +0 -2
  305. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +0 -2
  306. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +0 -2
  307. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +4 -2
  308. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +0 -2
  309. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +0 -2
  310. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +0 -2
  311. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +0 -2
  312. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +0 -2
  313. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +0 -2
  314. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +0 -2
  315. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +0 -2
  316. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +0 -2
  317. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +0 -2
  318. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +0 -2
  319. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +0 -2
  320. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +0 -2
  321. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +0 -2
  322. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +0 -2
  323. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +0 -2
  324. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +0 -2
  325. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +0 -2
  326. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +0 -2
  327. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +0 -2
  328. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +0 -2
  329. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +0 -2
  330. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +0 -2
  331. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +0 -2
  332. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +0 -2
  333. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +0 -2
  334. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +0 -2
  335. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +0 -2
  336. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +11 -13
  337. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +0 -2
  338. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +8 -10
  339. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +1 -2
  340. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +0 -2
  341. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +40 -27
  342. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +4 -6
  343. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +1 -3
  344. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +77 -7
  345. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +1 -3
  346. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +0 -3
  347. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +1 -3
  348. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +0 -2
  349. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +0 -2
  350. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +0 -2
  351. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +0 -2
  352. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +0 -2
  353. nautobot/project-static/docs/user-guide/index.html +89 -4
  354. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +0 -2
  355. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +0 -2
  356. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +0 -2
  357. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +0 -2
  358. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +0 -2
  359. nautobot/project-static/docs/user-guide/platform-functionality/events.html +0 -2
  360. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +0 -2
  361. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +0 -2
  362. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +0 -2
  363. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +0 -2
  364. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +0 -2
  365. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +0 -2
  366. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +0 -2
  367. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +0 -2
  368. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +0 -2
  369. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +0 -2
  370. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +0 -2
  371. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +0 -2
  372. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +0 -2
  373. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +0 -2
  374. nautobot/project-static/docs/user-guide/platform-functionality/note.html +0 -2
  375. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +0 -2
  376. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +0 -2
  377. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +0 -2
  378. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +0 -2
  379. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +0 -2
  380. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +0 -2
  381. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +0 -2
  382. nautobot/project-static/docs/user-guide/platform-functionality/role.html +0 -2
  383. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +0 -2
  384. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +0 -2
  385. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +0 -2
  386. nautobot/project-static/docs/user-guide/platform-functionality/status.html +0 -2
  387. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +0 -2
  388. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +0 -2
  389. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +0 -2
  390. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +0 -2
  391. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +207 -124
  392. nautobot/project-static/js/forms.js +88 -37
  393. nautobot/project-static/js/homepage_layout.js +12 -3
  394. nautobot/virtualization/forms.py +20 -0
  395. nautobot/virtualization/templates/virtualization/clustergroup.html +1 -39
  396. nautobot/virtualization/templates/virtualization/clustertype.html +1 -0
  397. nautobot/virtualization/tests/test_api.py +14 -3
  398. nautobot/virtualization/tests/test_views.py +10 -2
  399. nautobot/virtualization/urls.py +10 -93
  400. nautobot/virtualization/views.py +33 -72
  401. {nautobot-2.4.5.dist-info → nautobot-2.4.7.dist-info}/METADATA +6 -5
  402. {nautobot-2.4.5.dist-info → nautobot-2.4.7.dist-info}/RECORD +407 -402
  403. {nautobot-2.4.5.dist-info → nautobot-2.4.7.dist-info}/WHEEL +1 -1
  404. nautobot/core/tests/performance_baselines.yml +0 -8900
  405. nautobot/dcim/templates/dcim/modulebay_create.html +0 -39
  406. nautobot/ipam/tests/test_migrations.py +0 -462
  407. /nautobot/ipam/templates/ipam/{namespace_ipaddresses.html → namespace_ip_addresses.html} +0 -0
  408. {nautobot-2.4.5.dist-info → nautobot-2.4.7.dist-info}/LICENSE.txt +0 -0
  409. {nautobot-2.4.5.dist-info → nautobot-2.4.7.dist-info}/NOTICE +0 -0
  410. {nautobot-2.4.5.dist-info → nautobot-2.4.7.dist-info}/entry_points.txt +0 -0
@@ -2,7 +2,7 @@ import datetime
2
2
  import json
3
3
  import logging
4
4
  import re
5
- from urllib.parse import parse_qs
5
+ from urllib.parse import parse_qs, quote_plus
6
6
 
7
7
  from django import template
8
8
  from django.conf import settings
@@ -711,6 +711,20 @@ def render_ancestor_hierarchy(value):
711
711
  return result
712
712
 
713
713
 
714
+ @library.filter()
715
+ @register.filter()
716
+ def render_address(address):
717
+ if address:
718
+ map_link = format_html(
719
+ '<a href="https://maps.google.com/?q={}" target="_blank" class="btn btn-primary btn-xs">'
720
+ '<i class="mdi mdi-map-marker"></i> Map it</a>',
721
+ quote_plus(address),
722
+ )
723
+ address = format_html_join("", "{}<br>", ((line,) for line in address.split("\n")))
724
+ return format_html('<div class="pull-right noprint">{}</div>{}', map_link, address)
725
+ return HTML_NONE
726
+
727
+
714
728
  #
715
729
  # Tags
716
730
  #
@@ -702,6 +702,9 @@ class APIViewTestCases:
702
702
  else:
703
703
  self.assertEqual(obj.key, expected_slug)
704
704
 
705
+ # TODO: The override_settings here is a temporary workaround for not breaking any app tests
706
+ # long term fix should be using appropriate object permissions instead of the blanket override
707
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
705
708
  def test_create_object(self):
706
709
  """
707
710
  POST a single object with permission.
@@ -738,6 +741,9 @@ class APIViewTestCases:
738
741
  self.assertEqual(len(objectchanges), 1)
739
742
  self.assertEqual(objectchanges[0].action, extras_choices.ObjectChangeActionChoices.ACTION_CREATE)
740
743
 
744
+ # TODO: The override_settings here is a temporary workaround for not breaking any app tests
745
+ # long term fix should be using appropriate object permissions instead of the blanket override
746
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
741
747
  def test_recreate_object_csv(self):
742
748
  """CSV export an object, delete it, and recreate it via CSV import."""
743
749
  if hasattr(self, "get_deletable_object"):
@@ -801,6 +807,9 @@ class APIViewTestCases:
801
807
  f"{field_name} should have been unchanged on delete/recreate but it differs!",
802
808
  )
803
809
 
810
+ # TODO: The override_settings here is a temporary workaround for not breaking any app tests
811
+ # long term fix should be using appropriate object permissions instead of the blanket override
812
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
804
813
  def test_bulk_create_objects(self):
805
814
  """
806
815
  POST a set of objects in a single request.
@@ -853,6 +862,9 @@ class APIViewTestCases:
853
862
  response = self.client.patch(url, update_data, format="json", **self.header)
854
863
  self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
855
864
 
865
+ # TODO: The override_settings here is a temporary workaround for not breaking any app tests
866
+ # long term fix should be using appropriate object permissions instead of the blanket override
867
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
856
868
  def test_update_object(self):
857
869
  """
858
870
  PATCH a single object identified by its ID.
@@ -965,6 +977,9 @@ class APIViewTestCases:
965
977
  instance.refresh_from_db()
966
978
  self.assertInstanceEqual(instance, update_data, exclude=self.validation_excluded_fields, api=True)
967
979
 
980
+ # TODO: The override_settings here is a temporary workaround for not breaking any app tests
981
+ # long term fix should be using appropriate object permissions instead of the blanket override
982
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
968
983
  def test_get_put_round_trip(self):
969
984
  """GET and then PUT an object and verify that it's accepted and unchanged."""
970
985
  self.maxDiff = None
@@ -995,6 +1010,9 @@ class APIViewTestCases:
995
1010
  updated_serialized_object.pop("last_updated", None)
996
1011
  self.assertEqual(initial_serialized_object, updated_serialized_object)
997
1012
 
1013
+ # TODO: The override_settings here is a temporary workaround for not breaking any app tests
1014
+ # long term fix should be using appropriate object permissions instead of the blanket override
1015
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
998
1016
  def test_bulk_update_objects(self):
999
1017
  """
1000
1018
  PATCH a set of objects in a single request.
@@ -35,11 +35,15 @@ class ObjectsListMixin:
35
35
  """
36
36
  self.browser.find_by_css("#object_list_form input.toggle").click()
37
37
 
38
- def select_one_item(self):
38
+ def select_one_item(self, pk=None):
39
39
  """
40
40
  Click first row checkbox on items table list to select one row.
41
41
  """
42
- self.browser.find_by_css('#object_list_form input[name="pk"]').click()
42
+ selector = '#object_list_form input[name="pk"]'
43
+ if pk:
44
+ selector = f'{selector}[value="{pk}"]'
45
+
46
+ self.browser.find_by_css(selector).click()
43
47
 
44
48
  def set_per_page(self, per_page=1):
45
49
  """
@@ -44,8 +44,6 @@ CONSTANCE_BACKEND = "constance.backends.memory.MemoryBackend"
44
44
  TEST_USE_FACTORIES = True
45
45
  # For now, use a constant PRNG seed for consistent results. In the future we can remove this for fuzzier testing.
46
46
  TEST_FACTORY_SEED = "Nautobot"
47
- # File in which all performance-specifc test baselines are stored
48
- TEST_PERFORMANCE_BASELINE_FILE = "nautobot/core/tests/performance_baselines.yml"
49
47
 
50
48
  # Make Celery run synchronously (eager), to always store eager results, and run the broker in-memory.
51
49
  # NOTE: Celery does not honor the TASK_TRACK_STARTED config when running in eager mode, so the job result is not saved until after the task completes.
@@ -14,7 +14,6 @@ from django.db import connections
14
14
  from django.db.migrations.recorder import MigrationRecorder
15
15
  from django.test.runner import _init_worker, DiscoverRunner, ParallelTestSuite
16
16
  from django.test.utils import get_unique_databases_and_mirrors, NullTimeKeeper, override_settings
17
- import yaml
18
17
 
19
18
  from nautobot.core.celery import app, setup_nautobot_job_logging
20
19
  from nautobot.core.settings_funcs import parse_redis_connection
@@ -40,21 +39,25 @@ class NautobotParallelTestSuite(ParallelTestSuite):
40
39
 
41
40
  class NautobotTestRunner(DiscoverRunner):
42
41
  """
43
- Custom test runner that excludes integration tests by default.
42
+ Custom test runner that excludes (slow) integration and migration tests by default.
44
43
 
45
44
  This test runner is aware of our use of the "integration" tag and only runs integration tests if
46
45
  explicitly passed in with `nautobot-server test --tag integration`.
46
+ Similarly, it only runs migration tests if explicitly called with `--tag migration_test`.
47
47
 
48
48
  By Nautobot convention, integration tests must be tagged with "integration". The base
49
49
  `nautobot.core.testing.integration.SeleniumTestCase` has this tag, therefore any test cases
50
50
  inheriting from that class do not need to be explicitly tagged.
51
51
 
52
52
  Only integration tests that DO NOT inherit from `SeleniumTestCase` will need to be explicitly tagged.
53
+
54
+ Similarly, the `django-test-migrations` package `MigratorTestCase` base class has the tag `migration_test`, so
55
+ any subclasses thereof do not need to be explicitly tagged.
53
56
  """
54
57
 
55
58
  parallel_test_suite = NautobotParallelTestSuite
56
59
 
57
- exclude_tags = ["integration"]
60
+ exclude_tags = ["integration", "migration_test"]
58
61
 
59
62
  @classmethod
60
63
  def add_arguments(cls, parser):
@@ -75,15 +78,19 @@ class NautobotTestRunner(DiscoverRunner):
75
78
  self.cache_test_fixtures = cache_test_fixtures
76
79
  self.reusedb = reusedb
77
80
 
78
- # Assert "integration" hasn't been provided w/ --tag
79
81
  incoming_tags = kwargs.get("tags") or []
80
- # Assert "exclude_tags" hasn't been provided w/ --exclude-tag; else default to our own.
81
- incoming_exclude_tags = kwargs.get("exclude_tags") or []
82
+ exclude_tags = kwargs.get("exclude_tags") or []
83
+
84
+ for default_excluded_tag in self.exclude_tags:
85
+ if default_excluded_tag not in incoming_tags:
86
+ exclude_tags.append(default_excluded_tag)
87
+ # Can't just use self.log() here because we haven't yet called super().__init__()
88
+ if logger := kwargs.get("logger"):
89
+ logger.info("Implicitly excluding tests tagged %r", default_excluded_tag)
90
+ elif kwargs.get("verbosity", 1) >= 1:
91
+ print(f"Implicitly excluding tests tagged {default_excluded_tag!r}")
82
92
 
83
- # Only include our excluded tags if "integration" isn't provided w/ --tag
84
- if "integration" not in incoming_tags:
85
- incoming_exclude_tags.extend(self.exclude_tags)
86
- kwargs["exclude_tags"] = incoming_exclude_tags
93
+ kwargs["exclude_tags"] = exclude_tags
87
94
 
88
95
  super().__init__(**kwargs)
89
96
 
@@ -202,133 +209,3 @@ class NautobotTestRunner(DiscoverRunner):
202
209
  print(f"Database {db_name} emptied!")
203
210
 
204
211
  connection.creation.destroy_test_db(old_name, self.verbosity, self.keepdb)
205
-
206
-
207
- # Use django_slowtests only when GENERATE_PERFORMANCE_REPORT flag is set to true
208
- try:
209
- from django_slowtests.testrunner import DiscoverSlowestTestsRunner
210
-
211
- print("Using NautobotPerformanceTestRunner to run tests ...")
212
-
213
- class NautobotPerformanceTestRunner(NautobotTestRunner, DiscoverSlowestTestsRunner):
214
- """
215
- Pre-requisite:
216
- Set `GENERATE_PERFORMANCE_REPORT` to True in settings.py
217
- This test runner is designated to run performance specific unit tests.
218
-
219
- `ModelViewTestCase` is tagged with `performance` to test the time it will take to retrieve, list, create, bulk_create,
220
- delete, bulk_delete, edit, bulk_edit object(s) and various other operations.
221
-
222
- The results are compared to the corresponding entries in `TEST_PERFORMANCE_BASELINE_FILE` and only results that are significantly slower
223
- than baseline will be exposed to the user.
224
- """
225
-
226
- def generate_report(self, test_results, result):
227
- """
228
- Generate Performance Report consists of unit tests that are significantly slower than baseline.
229
- """
230
- test_result_count = len(test_results)
231
-
232
- # Add `--performance-snapshot` to the end of `invoke` commands to generate a report.json file consist of the performance tests result
233
- if self.report_path:
234
- data = [
235
- {
236
- "tests": [
237
- {
238
- "name": func_name,
239
- "execution_time": float(timing),
240
- }
241
- for func_name, timing in test_results
242
- ],
243
- "test_count": result.testsRun,
244
- "failed_count": len(result.errors + result.failures),
245
- "total_execution_time": result.timeTaken,
246
- }
247
- ]
248
- with open(self.report_path, "w") as outfile:
249
- yaml.dump(data, outfile, sort_keys=False)
250
- # Print the results in the CLI.
251
- else:
252
- if test_result_count:
253
- print(f"\n{test_result_count} abnormally slower tests:")
254
- for func_name, timing in test_results:
255
- time = float(timing)
256
- baseline = self.baselines.get(func_name, None)
257
- if baseline:
258
- baseline = float(baseline)
259
- print(f"{time:.4f}s {func_name} is significantly slower than the baseline {baseline:.4f}s")
260
- else:
261
- print(
262
- f"Performance baseline for {func_name} is not available. Test took {time:.4f}s to run"
263
- )
264
-
265
- if not test_results:
266
- print("\nNo tests signficantly slower than baseline. Success!")
267
-
268
- def get_baselines(self):
269
- """Load the performance_baselines.yml file for result comparison."""
270
- baselines = {}
271
- input_file = getattr(
272
- settings, "TEST_PERFORMANCE_BASELINE_FILE", "nautobot/core/tests/performance_baselines.yml"
273
- )
274
-
275
- with open(input_file) as f:
276
- data = yaml.safe_load(f)
277
- for entry in data["tests"]:
278
- baselines[entry["name"]] = entry["execution_time"]
279
- return baselines
280
-
281
- def suite_result(self, suite, result):
282
- """Compile the performance test results"""
283
- return_value = super(DiscoverSlowestTestsRunner, self).suite_result(suite, result)
284
- self.baselines = self.get_baselines()
285
-
286
- # add `--performance_report` to `invoke` commands to generate report.
287
- # e.g. `invoke unittest --performance_report`
288
- if not self.should_generate_report:
289
- self.remove_timing_tmp_files()
290
- return return_value
291
-
292
- # Grab slowest tests
293
- timings = self.get_timings()
294
- # Sort the results by test names x[0]
295
- by_name = sorted(timings, key=lambda x: x[0])
296
- test_results = by_name
297
-
298
- if self.baselines:
299
- # Filter tests by baseline numbers
300
- test_results = []
301
-
302
- for entry in by_name:
303
- # Convert test time from seconds to miliseconds for comparison
304
- result_time_ms = entry[1] * 1000
305
- # If self.report_path, that means the user wants to update the performance baselines.
306
- # so we append every result that is available to us.
307
- if self.report_path:
308
- test_results.append(entry)
309
- else:
310
- # If the test is completed under 1.5 times the baseline or the difference between the result and the baseline is less than 3 seconds,
311
- # dont show the test to the user.
312
-
313
- baseline = self.baselines.get(entry[0], None)
314
-
315
- # check if baseline is available
316
- if not baseline:
317
- test_results.append(entry)
318
- continue
319
-
320
- # baseline duration in milliseconds
321
- baseline_ms = baseline * 1000
322
- # Arbitrary criteria to not make performance test fail easily
323
- if result_time_ms <= baseline_ms * 1.5 or result_time_ms - baseline_ms <= 500:
324
- continue
325
-
326
- test_results.append(entry)
327
-
328
- self.generate_report(test_results, result)
329
- return return_value
330
-
331
- except ImportError:
332
- print(
333
- "Unable to import DiscoverSlowestTestsRunner from `django_slowtests`. Is the 'django_slowtests' package installed?"
334
- )
@@ -639,7 +639,7 @@ class WritableNestedSerializerTest(testing.APITestCase):
639
639
  "vlan_group": self.vlan_group1.pk,
640
640
  }
641
641
  url = reverse("ipam-api:vlan-list")
642
- self.add_permissions("ipam.add_vlan")
642
+ self.add_permissions("ipam.add_vlan", "ipam.view_vlangroup", "extras.view_status")
643
643
 
644
644
  response = self.client.post(url, data, format="json", **self.header)
645
645
  self.assertHttpStatus(response, status.HTTP_201_CREATED)
@@ -672,7 +672,7 @@ class WritableNestedSerializerTest(testing.APITestCase):
672
672
  "vlan_group": {"name": self.vlan_group1.name},
673
673
  }
674
674
  url = reverse("ipam-api:vlan-list")
675
- self.add_permissions("ipam.add_vlan")
675
+ self.add_permissions("ipam.add_vlan", "ipam.view_vlangroup", "extras.view_status")
676
676
 
677
677
  response = self.client.post(url, data, format="json", **self.header)
678
678
  self.assertHttpStatus(response, status.HTTP_201_CREATED)
@@ -708,7 +708,7 @@ class WritableNestedSerializerTest(testing.APITestCase):
708
708
  },
709
709
  }
710
710
  url = reverse("ipam-api:vlan-list")
711
- self.add_permissions("ipam.add_vlan")
711
+ self.add_permissions("ipam.add_vlan", "ipam.view_vlangroup", "extras.view_status")
712
712
 
713
713
  with testing.disable_warnings("django.request"):
714
714
  response = self.client.post(url, data, format="json", **self.header)
@@ -775,7 +775,7 @@ class WritableNestedSerializerTest(testing.APITestCase):
775
775
  "vlan_group": self.vlan_group1.pk,
776
776
  }
777
777
  url = reverse("ipam-api:vlan-list")
778
- self.add_permissions("ipam.add_vlan")
778
+ self.add_permissions("ipam.add_vlan", "ipam.view_vlangroup", "extras.view_status")
779
779
 
780
780
  response = self.client.post(url, data, format="json", **self.header)
781
781
  self.assertHttpStatus(response, status.HTTP_201_CREATED)
@@ -383,14 +383,31 @@ class ObjectPermissionAPIViewTestCase(TestCase):
383
383
  )
384
384
  obj_perm.users.add(self.user)
385
385
  obj_perm.object_types.add(ContentType.objects.get_for_model(Prefix))
386
-
386
+ related_obj_perm = ObjectPermission.objects.create(
387
+ name="Related object permission",
388
+ actions=["view"],
389
+ )
390
+ related_obj_perm.users.add(self.user)
391
+ related_obj_perm.object_types.add(
392
+ ContentType.objects.get_for_model(Namespace),
393
+ ContentType.objects.get_for_model(Status),
394
+ ContentType.objects.get_for_model(Location),
395
+ )
387
396
  # Attempt to create a non-permitted object
388
397
  response = self.client.post(url, data, format="json", **self.header)
389
398
  self.assertEqual(response.status_code, 403)
390
399
  self.assertEqual(Prefix.objects.count(), initial_count)
391
400
 
392
- # Create a permitted object
401
+ # Attempt to create a permitted object without related object permissions
393
402
  data["location"] = self.locations[0].pk
403
+ related_obj_perm.users.remove(self.user)
404
+ response = self.client.post(url, data, format="json", **self.header)
405
+ self.assertEqual(response.status_code, 400)
406
+ self.assertIn(b"Related object not found using the provided attribute", response.content)
407
+ self.assertEqual(Prefix.objects.count(), initial_count)
408
+
409
+ # Create a permitted object with related object permissions
410
+ related_obj_perm.users.add(self.user)
394
411
  response = self.client.post(url, data, format="json", **self.header)
395
412
  self.assertEqual(response.status_code, 201)
396
413
  self.assertEqual(Prefix.objects.count(), initial_count + 1)
@@ -411,17 +428,33 @@ class ObjectPermissionAPIViewTestCase(TestCase):
411
428
  )
412
429
  obj_perm.users.add(self.user)
413
430
  obj_perm.object_types.add(ContentType.objects.get_for_model(Prefix))
414
-
431
+ related_obj_perm = ObjectPermission.objects.create(
432
+ name="Related object permission",
433
+ actions=["view"],
434
+ )
435
+ related_obj_perm.users.add(self.user)
436
+ related_obj_perm.object_types.add(
437
+ ContentType.objects.get_for_model(Namespace),
438
+ ContentType.objects.get_for_model(Status),
439
+ ContentType.objects.get_for_model(Location),
440
+ )
415
441
  # Attempt to edit a non-permitted object
416
442
  data = {"location": self.locations[0].pk}
417
443
  url = reverse("ipam-api:prefix-detail", kwargs={"pk": self.prefixes[3].pk})
418
444
  response = self.client.patch(url, data, format="json", **self.header)
419
445
  self.assertEqual(response.status_code, 404)
420
446
 
421
- # Edit a permitted object
447
+ # Attempt to edit a permitted object without related object permissions
448
+ related_obj_perm.users.remove(self.user)
422
449
  data["status"] = self.statuses[1].pk
423
450
  url = reverse("ipam-api:prefix-detail", kwargs={"pk": self.prefixes[0].pk})
424
451
  response = self.client.patch(url, data, format="json", **self.header)
452
+ self.assertEqual(response.status_code, 400)
453
+ self.assertIn(b"Related object not found using the provided attribute", response.content)
454
+
455
+ # Edit a permitted object with related object permissions
456
+ related_obj_perm.users.add(self.user)
457
+ response = self.client.patch(url, data, format="json", **self.header)
425
458
  self.assertEqual(response.status_code, 200)
426
459
 
427
460
  # Attempt to modify a permitted object to a non-permitted object
@@ -456,6 +489,32 @@ class ObjectPermissionAPIViewTestCase(TestCase):
456
489
  response = self.client.delete(url, format="json", **self.header)
457
490
  self.assertEqual(response.status_code, 204)
458
491
 
492
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
493
+ def test_related_object_permission_constraints_on_get_requests(self):
494
+ """
495
+ Users who have permission to view Location objects, but not LocationType and Status objects
496
+ should still be able to view Location objects from the API.
497
+ """
498
+ self.add_permissions("dcim.view_location")
499
+ response = self.client.get(reverse("dcim-api:location-list"), **self.header)
500
+ self.assertEqual(response.status_code, 200)
501
+ # we should be able to get all the locations
502
+ self.assertEqual(len(response.data["results"]), Location.objects.count())
503
+
504
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
505
+ def test_related_object_permission_constraints_on_patch_requests(self):
506
+ """
507
+ Users who have permission to view and change Location objects, but not LocationType and Status objects
508
+ should still be able to change a Location object's name from the API.
509
+ """
510
+ self.add_permissions("dcim.view_location", "dcim.change_location")
511
+ location = Location.objects.first()
512
+ data = {"name": "New Location Name"}
513
+ url = reverse("dcim-api:location-detail", kwargs={"pk": location.pk})
514
+ response = self.client.patch(url, data, format="json", **self.header)
515
+ self.assertEqual(response.status_code, 200)
516
+ self.assertEqual(response.data["name"], "New Location Name")
517
+
459
518
  @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
460
519
  def test_user_token_constraints(self):
461
520
  """
@@ -487,6 +546,16 @@ class ObjectPermissionAPIViewTestCase(TestCase):
487
546
  )
488
547
  obj_perm.users.add(self.user, obj_user2)
489
548
  obj_perm.object_types.add(ContentType.objects.get_for_model(Prefix))
549
+ related_obj_perm = ObjectPermission.objects.create(
550
+ name="Related object permission",
551
+ actions=["view"],
552
+ )
553
+ related_obj_perm.users.add(self.user, obj_user2)
554
+ related_obj_perm.object_types.add(
555
+ ContentType.objects.get_for_model(Namespace),
556
+ ContentType.objects.get_for_model(Status),
557
+ ContentType.objects.get_for_model(Location),
558
+ )
490
559
  # Create one Prefix object per user
491
560
  self.client.post(url, data[0], format="json", **self.header)
492
561
  self.client.post(url, data[1], format="json", **header_user2)
@@ -550,6 +619,16 @@ class ObjectPermissionAPIViewTestCase(TestCase):
550
619
  )
551
620
  obj_perm.users.add(self.user, obj_user2)
552
621
  obj_perm.object_types.add(ContentType.objects.get_for_model(Prefix))
622
+ related_obj_perm = ObjectPermission.objects.create(
623
+ name="Related object permission",
624
+ actions=["view"],
625
+ )
626
+ related_obj_perm.users.add(self.user, obj_user2)
627
+ related_obj_perm.object_types.add(
628
+ ContentType.objects.get_for_model(Namespace),
629
+ ContentType.objects.get_for_model(Status),
630
+ ContentType.objects.get_for_model(Location),
631
+ )
553
632
  # Create one Prefix object per user
554
633
  self.client.post(url, data[0], format="json", **self.header)
555
634
  self.client.post(url, data[1], format="json", **header_user2)
@@ -486,20 +486,23 @@ class NumericArrayFieldTest(TestCase):
486
486
  self.field = dcim_forms.DeviceFilterForm().fields["device_redundancy_group_priority"]
487
487
 
488
488
  def test_valid_input(self):
489
- # Mapping of input => expected
490
- tests = {
491
- None: [],
492
- "": [],
493
- "80,443-444": [80, 443, 444],
494
- "1024-1028,31337": [1024, 1025, 1026, 1027, 1028, 31337],
495
- }
496
- for test, expected in tests.items():
489
+ # List of (input, expected output) tuples
490
+ tests = [
491
+ (None, []),
492
+ ("", []),
493
+ ("80,443-444", [80, 443, 444]),
494
+ ("1024-1028,31337", [1024, 1025, 1026, 1027, 1028, 31337]),
495
+ (["47-49", "103"], [47, 48, 49, 103]),
496
+ ([231, 432, 313], [231, 313, 432]),
497
+ ]
498
+ for test, expected in tests:
497
499
  self.assertEqual(self.field.clean(test), expected)
498
500
 
499
501
  def test_invalid_input(self):
500
502
  tests = [
501
503
  "pizza",
502
504
  "-41",
505
+ "[84,52,33]",
503
506
  ]
504
507
  for test in tests:
505
508
  with self.assertRaises(django_forms.ValidationError):
@@ -1,3 +1,4 @@
1
+ import contextlib
1
2
  import datetime
2
3
  import random
3
4
  import types
@@ -8,6 +9,7 @@ from django.apps import apps
8
9
  from django.contrib.auth import get_user_model
9
10
  from django.contrib.auth.models import Group
10
11
  from django.contrib.contenttypes.models import ContentType
12
+ from django.core.cache import cache
11
13
  from django.db.models import Count, Q
12
14
  from django.test import override_settings, TestCase
13
15
  from django.test.client import RequestFactory
@@ -17,6 +19,7 @@ from graphene_django.registry import get_global_registry
17
19
  from graphene_django.settings import graphene_settings
18
20
  from graphql import get_default_backend, GraphQLError
19
21
  from graphql.error.located_error import GraphQLLocatedError
22
+ import redis.exceptions
20
23
  from rest_framework import status
21
24
 
22
25
  from nautobot.circuits.models import CircuitTermination, Provider
@@ -396,6 +399,12 @@ class GraphQLExtendSchemaRelationship(GraphQLTestCaseBase):
396
399
  self.location_schema = generate_schema_type(app_name="dcim", model=Location)
397
400
  self.vlan_schema = generate_schema_type(app_name="ipam", model=VLAN)
398
401
 
402
+ def tearDown(self):
403
+ """Ensure that relationship caches are cleared to avoid leakage into other tests."""
404
+ with contextlib.suppress(redis.exceptions.ConnectionError):
405
+ cache.delete_pattern(f"{Relationship.objects.get_for_model_source.cache_key_prefix}.*")
406
+ cache.delete_pattern(f"{Relationship.objects.get_for_model_destination.cache_key_prefix}.*")
407
+
399
408
  def test_extend_relationship_default_prefix(self):
400
409
  """Verify that relationships are correctly added to the schema."""
401
410
  schema = extend_schema_type_relationships(self.vlan_schema, VLAN)
@@ -353,6 +353,13 @@ class ImportObjectsTestCase(TransactionTestCase):
353
353
  self.assertEqual(log_successes[4].message, "Created 4 status object(s) from 5 row(s) of data")
354
354
 
355
355
  def test_csv_import_contact_assignment(self):
356
+ self.add_permissions(
357
+ "dcim.view_locationtype",
358
+ "extras.view_status",
359
+ "dcim.view_location",
360
+ "extras.add_role",
361
+ "extras.add_contact",
362
+ )
356
363
  location_types_csv = "\n".join(["name", "ContactAssignmentImportTestLocationType"])
357
364
  locations_csv = "\n".join(
358
365
  [
@@ -8,7 +8,7 @@ import logging
8
8
  from django.contrib.contenttypes.models import ContentType
9
9
  from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist
10
10
  from django.db import models
11
- from django.db.models import CharField, JSONField, URLField
11
+ from django.db.models import CharField, JSONField, Q, URLField
12
12
  from django.db.models.fields.related import ManyToManyField
13
13
  from django.template import Context
14
14
  from django.template.defaultfilters import truncatechars
@@ -375,6 +375,11 @@ class Tab(Component):
375
375
  class DistinctViewTab(Tab):
376
376
  """
377
377
  A Tab that doesn't render inline on the same page, but instead links to a distinct view of its own when clicked.
378
+
379
+ Args:
380
+ url_name (str): The name of the URL pattern to link to, which will be reversed to generate the URL.
381
+ label_wrapper_template_path (str, optional): Template path to render the tab label to HTML.
382
+ related_object_attribute (str, optional): The name of the related object attribute to count for the tab label.
378
383
  """
379
384
 
380
385
  def __init__(
@@ -382,9 +387,11 @@ class DistinctViewTab(Tab):
382
387
  *,
383
388
  url_name,
384
389
  label_wrapper_template_path="components/tab/label_wrapper_distinct_view.html",
390
+ related_object_attribute="",
385
391
  **kwargs,
386
392
  ):
387
393
  self.url_name = url_name
394
+ self.related_object_attribute = related_object_attribute
388
395
  super().__init__(label_wrapper_template_path=label_wrapper_template_path, **kwargs)
389
396
 
390
397
  def get_extra_context(self, context: Context):
@@ -393,6 +400,30 @@ class DistinctViewTab(Tab):
393
400
  def render(self, context: Context):
394
401
  return ""
395
402
 
403
+ def render_label(self, context: Context):
404
+ if not self.related_object_attribute:
405
+ return super().render_label(context)
406
+
407
+ obj = get_obj_from_context(context)
408
+ if not hasattr(obj, self.related_object_attribute):
409
+ logger.warning(
410
+ f"{obj} does not have a related attribute {self.related_object_attribute} to count for tab label."
411
+ )
412
+ return super().render_label(context)
413
+
414
+ try:
415
+ related_obj_count = getattr(obj, self.related_object_attribute).count()
416
+ return format_html(
417
+ "{} {}",
418
+ self.label,
419
+ render_to_string("utilities/templatetags/badge.html", badge(related_obj_count)),
420
+ )
421
+ except AttributeError:
422
+ logger.warning(
423
+ f"{obj}'s attribute {self.related_object_attribute} is not a related manager to count for tab label."
424
+ )
425
+ return super().render_label(context)
426
+
396
427
 
397
428
  class Panel(Component):
398
429
  """Base class for defining an individual display panel within a Layout within a Tab."""
@@ -634,10 +665,14 @@ class ObjectsTablePanel(Panel):
634
665
  Table (`BaseTable`) instance. Mutually exclusive with `table_class`, `table_filter`, `table_attribute`.
635
666
  table_class (obj): The table class that will be instantiated and rendered e.g. CircuitTable, DeviceTable.
636
667
  Mutually exclusive with `context_table_key`.
637
- table_filter (str, optional): The name of the filter to apply to the queryset to initialize the table class.
668
+ table_filter (str, list, optional): The filter(s) to apply to the queryset to initialize the table class.
638
669
  For example, in a LocationType detail view, for an ObjectsTablePanel of related Locations, this would
639
670
  be `location_type`, because `Location.objects.filter(location_type=obj)` gives the desired queryset.
640
671
  Mutually exclusive with `table_attribute`.
672
+ For example, in ProviderNetwork detail view, for an ObjectsTablePanel of related Circuits, this would
673
+ be `["circuit_termination_a__provider_network", "circuit_termination_z__provider_network"]` because
674
+ `Circuit.objects.filter(Q(circuit_termination_a__provider_network=instance)
675
+ | Q(circuit_termination_z__provider_network=instance))` gives the desired queryset.
641
676
  table_attribute (str, optional): The attribute of the detail view instance that contains the queryset to
642
677
  initialize the table class. e.g. `dynamic_groups`.
643
678
  Mutually exclusive with `table_filter`.
@@ -758,7 +793,16 @@ class ObjectsTablePanel(Panel):
758
793
  if self.table_attribute:
759
794
  body_content_table_queryset = getattr(instance, self.table_attribute)
760
795
  else:
761
- body_content_table_queryset = body_content_table_model.objects.filter(**{self.table_filter: instance})
796
+ if isinstance(self.table_filter, str):
797
+ table_filters = [self.table_filter]
798
+ elif isinstance(self.table_filter, list):
799
+ table_filters = self.table_filter
800
+ else:
801
+ table_filters = []
802
+ query = Q()
803
+ for table_filter in table_filters:
804
+ query = query | Q(**{table_filter: instance})
805
+ body_content_table_queryset = body_content_table_model.objects.filter(query)
762
806
 
763
807
  body_content_table_queryset = body_content_table_queryset.restrict(request.user, "view")
764
808
  if self.select_related_fields: