nautobot 2.2.2__py3-none-any.whl → 2.2.4__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 (362) hide show
  1. nautobot/apps/jobs.py +2 -0
  2. nautobot/core/api/utils.py +12 -9
  3. nautobot/core/apps/__init__.py +2 -2
  4. nautobot/core/celery/__init__.py +79 -68
  5. nautobot/core/celery/backends.py +9 -1
  6. nautobot/core/celery/control.py +4 -7
  7. nautobot/core/celery/schedulers.py +4 -2
  8. nautobot/core/celery/task.py +78 -5
  9. nautobot/core/graphql/schema.py +2 -1
  10. nautobot/core/jobs/__init__.py +2 -1
  11. nautobot/core/settings.py +6 -4
  12. nautobot/core/settings.yaml +51 -16
  13. nautobot/core/templates/admin/base.html +2 -2
  14. nautobot/core/templates/base_django.html +2 -2
  15. nautobot/core/templates/buttons/export.html +47 -47
  16. nautobot/core/templates/generic/object_list.html +3 -3
  17. nautobot/core/templates/inc/javascript.html +3 -0
  18. nautobot/core/templates/inc/media.html +3 -0
  19. nautobot/core/templates/login.html +2 -2
  20. nautobot/core/templates/nautobot_config.py.j2 +2 -0
  21. nautobot/core/templatetags/helpers.py +66 -9
  22. nautobot/core/testing/__init__.py +6 -1
  23. nautobot/core/testing/api.py +12 -13
  24. nautobot/core/testing/mixins.py +2 -2
  25. nautobot/core/testing/views.py +50 -51
  26. nautobot/core/tests/test_api.py +23 -2
  27. nautobot/core/tests/test_jobs.py +79 -2
  28. nautobot/core/tests/test_templatetags_helpers.py +32 -0
  29. nautobot/core/tests/test_views.py +52 -0
  30. nautobot/core/tests/test_views_utils.py +22 -1
  31. nautobot/core/utils/module_loading.py +89 -0
  32. nautobot/core/views/mixins.py +4 -0
  33. nautobot/core/views/utils.py +3 -2
  34. nautobot/dcim/choices.py +14 -0
  35. nautobot/dcim/forms.py +51 -1
  36. nautobot/dcim/models/device_components.py +9 -5
  37. nautobot/dcim/templates/dcim/location.html +32 -13
  38. nautobot/dcim/templates/dcim/location_migrate_data_to_contact.html +102 -0
  39. nautobot/dcim/tests/test_views.py +376 -55
  40. nautobot/dcim/urls.py +5 -0
  41. nautobot/dcim/views.py +172 -21
  42. nautobot/extras/api/serializers.py +17 -6
  43. nautobot/extras/api/views.py +21 -10
  44. nautobot/extras/constants.py +3 -3
  45. nautobot/extras/datasources/git.py +47 -58
  46. nautobot/extras/forms/forms.py +3 -1
  47. nautobot/extras/jobs.py +79 -146
  48. nautobot/extras/models/datasources.py +0 -2
  49. nautobot/extras/models/jobs.py +36 -18
  50. nautobot/extras/plugins/__init__.py +1 -20
  51. nautobot/extras/signals.py +6 -9
  52. nautobot/extras/test_jobs/__init__.py +8 -0
  53. nautobot/extras/test_jobs/dry_run.py +3 -2
  54. nautobot/extras/test_jobs/fail.py +43 -0
  55. nautobot/extras/test_jobs/ipaddress_vars.py +40 -1
  56. nautobot/extras/test_jobs/jobs_module/__init__.py +5 -0
  57. nautobot/extras/test_jobs/jobs_module/jobs_submodule/__init__.py +1 -0
  58. nautobot/extras/test_jobs/jobs_module/jobs_submodule/jobs.py +6 -0
  59. nautobot/extras/test_jobs/pass.py +40 -0
  60. nautobot/extras/test_jobs/relative_import.py +11 -0
  61. nautobot/extras/tests/test_api.py +3 -0
  62. nautobot/extras/tests/test_context_managers.py +18 -0
  63. nautobot/extras/tests/test_datasources.py +125 -118
  64. nautobot/extras/tests/test_job_variables.py +57 -15
  65. nautobot/extras/tests/test_jobs.py +135 -1
  66. nautobot/extras/tests/test_models.py +26 -19
  67. nautobot/extras/tests/test_plugins.py +1 -3
  68. nautobot/extras/tests/test_views.py +2 -4
  69. nautobot/extras/utils.py +2 -1
  70. nautobot/extras/views.py +82 -116
  71. nautobot/ipam/api/views.py +8 -1
  72. nautobot/ipam/graphql/types.py +11 -0
  73. nautobot/ipam/mixins.py +32 -0
  74. nautobot/ipam/models.py +2 -1
  75. nautobot/ipam/querysets.py +6 -1
  76. nautobot/ipam/tests/test_models.py +82 -0
  77. nautobot/ipam/views.py +6 -6
  78. nautobot/project-static/docs/404.html +107 -51
  79. nautobot/project-static/docs/apps/index.html +107 -51
  80. nautobot/project-static/docs/apps/nautobot-apps.html +107 -51
  81. nautobot/project-static/docs/assets/_mkdocstrings.css +6 -1
  82. nautobot/project-static/docs/assets/extra.css +11 -0
  83. nautobot/project-static/docs/assets/javascripts/bundle.3220b9d7.min.js +29 -0
  84. nautobot/project-static/docs/assets/javascripts/bundle.3220b9d7.min.js.map +7 -0
  85. nautobot/project-static/docs/assets/stylesheets/main.66ac8b77.min.css +1 -0
  86. nautobot/project-static/docs/assets/stylesheets/main.66ac8b77.min.css.map +1 -0
  87. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +107 -51
  88. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +107 -51
  89. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +108 -52
  90. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +107 -51
  91. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +107 -51
  92. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +107 -51
  93. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +107 -51
  94. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +107 -51
  95. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +107 -51
  96. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +107 -51
  97. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +107 -51
  98. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +107 -51
  99. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +107 -51
  100. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +287 -262
  101. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +107 -51
  102. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +107 -51
  103. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +107 -51
  104. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +107 -51
  105. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +107 -51
  106. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +107 -51
  107. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +107 -51
  108. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +107 -51
  109. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +107 -51
  110. nautobot/project-static/docs/development/apps/api/configuration-view.html +110 -54
  111. nautobot/project-static/docs/development/apps/api/database-backend-config.html +110 -54
  112. nautobot/project-static/docs/development/apps/api/models/django-admin.html +107 -51
  113. nautobot/project-static/docs/development/apps/api/models/global-search.html +110 -54
  114. nautobot/project-static/docs/development/apps/api/models/graphql.html +113 -57
  115. nautobot/project-static/docs/development/apps/api/models/index.html +107 -51
  116. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +113 -57
  117. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +107 -51
  118. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +110 -54
  119. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +107 -51
  120. nautobot/project-static/docs/development/apps/api/platform-features/index.html +107 -51
  121. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +110 -54
  122. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +111 -55
  123. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +110 -54
  124. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +110 -54
  125. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +107 -51
  126. nautobot/project-static/docs/development/apps/api/prometheus.html +110 -54
  127. nautobot/project-static/docs/development/apps/api/setup.html +107 -51
  128. nautobot/project-static/docs/development/apps/api/testing.html +113 -57
  129. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +110 -54
  130. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +110 -54
  131. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +107 -51
  132. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +107 -51
  133. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +113 -57
  134. nautobot/project-static/docs/development/apps/api/views/base-template.html +107 -51
  135. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +110 -54
  136. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +107 -51
  137. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +110 -54
  138. nautobot/project-static/docs/development/apps/api/views/index.html +107 -51
  139. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +113 -57
  140. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +122 -66
  141. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +110 -54
  142. nautobot/project-static/docs/development/apps/api/views/notes.html +110 -54
  143. nautobot/project-static/docs/development/apps/api/views/rest-api.html +107 -51
  144. nautobot/project-static/docs/development/apps/api/views/urls.html +107 -51
  145. nautobot/project-static/docs/development/apps/index.html +128 -72
  146. nautobot/project-static/docs/development/apps/migration/code-updates.html +107 -51
  147. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +107 -51
  148. nautobot/project-static/docs/development/apps/migration/from-v1.html +107 -51
  149. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +107 -51
  150. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +107 -51
  151. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +107 -51
  152. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +107 -51
  153. nautobot/project-static/docs/development/apps/porting-from-netbox.html +110 -54
  154. nautobot/project-static/docs/development/core/application-registry.html +242 -144
  155. nautobot/project-static/docs/development/core/best-practices.html +122 -66
  156. nautobot/project-static/docs/development/core/bootstrap-ui.html +107 -51
  157. nautobot/project-static/docs/development/core/caching.html +107 -51
  158. nautobot/project-static/docs/development/core/controllers.html +107 -51
  159. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +113 -57
  160. nautobot/project-static/docs/development/core/generic-views.html +110 -54
  161. nautobot/project-static/docs/development/core/getting-started.html +135 -79
  162. nautobot/project-static/docs/development/core/homepage.html +110 -54
  163. nautobot/project-static/docs/development/core/index.html +107 -51
  164. nautobot/project-static/docs/development/core/model-checklist.html +156 -52
  165. nautobot/project-static/docs/development/core/model-features.html +108 -52
  166. nautobot/project-static/docs/development/core/natural-keys.html +110 -54
  167. nautobot/project-static/docs/development/core/navigation-menu.html +107 -51
  168. nautobot/project-static/docs/development/core/release-checklist.html +107 -51
  169. nautobot/project-static/docs/development/core/role-internals.html +107 -51
  170. nautobot/project-static/docs/development/core/settings.html +107 -51
  171. nautobot/project-static/docs/development/core/style-guide.html +110 -54
  172. nautobot/project-static/docs/development/core/templates.html +113 -57
  173. nautobot/project-static/docs/development/core/testing.html +125 -69
  174. nautobot/project-static/docs/development/core/user-preferences.html +107 -51
  175. nautobot/project-static/docs/development/index.html +107 -51
  176. nautobot/project-static/docs/development/jobs/index.html +504 -172
  177. nautobot/project-static/docs/development/jobs/migration/from-v1.html +111 -55
  178. nautobot/project-static/docs/docker/index.html +3 -3
  179. nautobot/project-static/docs/index.html +125 -69
  180. nautobot/project-static/docs/installation/selinux-troubleshooting.html +3 -3
  181. nautobot/project-static/docs/objects.inv +0 -0
  182. nautobot/project-static/docs/release-notes/index.html +107 -51
  183. nautobot/project-static/docs/release-notes/version-1.0.html +107 -51
  184. nautobot/project-static/docs/release-notes/version-1.1.html +107 -51
  185. nautobot/project-static/docs/release-notes/version-1.2.html +107 -51
  186. nautobot/project-static/docs/release-notes/version-1.3.html +107 -51
  187. nautobot/project-static/docs/release-notes/version-1.4.html +107 -51
  188. nautobot/project-static/docs/release-notes/version-1.5.html +116 -60
  189. nautobot/project-static/docs/release-notes/version-1.6.html +107 -51
  190. nautobot/project-static/docs/release-notes/version-2.0.html +110 -54
  191. nautobot/project-static/docs/release-notes/version-2.1.html +107 -51
  192. nautobot/project-static/docs/release-notes/version-2.2.html +500 -114
  193. nautobot/project-static/docs/requirements.txt +2 -2
  194. nautobot/project-static/docs/search/search_index.json +1 -1
  195. nautobot/project-static/docs/sitemap.xml +262 -262
  196. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  197. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +107 -51
  198. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +107 -51
  199. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +107 -51
  200. nautobot/project-static/docs/user-guide/administration/configuration/index.html +107 -51
  201. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +251 -164
  202. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +113 -57
  203. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +107 -51
  204. nautobot/project-static/docs/user-guide/administration/guides/caching.html +113 -57
  205. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +107 -51
  206. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +107 -51
  207. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +107 -51
  208. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +113 -57
  209. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +107 -51
  210. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +107 -51
  211. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +107 -51
  212. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +171 -112
  213. nautobot/project-static/docs/user-guide/administration/installation/docker.html +13 -8626
  214. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +117 -61
  215. nautobot/project-static/docs/user-guide/administration/installation/health-checks.html +13 -8614
  216. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +252 -165
  217. nautobot/project-static/docs/user-guide/administration/installation/index.html +165 -192
  218. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +411 -691
  219. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +248 -229
  220. nautobot/project-static/docs/user-guide/administration/installation/selinux-troubleshooting.html +13 -8118
  221. nautobot/project-static/docs/user-guide/administration/installation/services.html +350 -240
  222. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +8684 -0
  223. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +8672 -0
  224. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +8176 -0
  225. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +110 -54
  226. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +110 -54
  227. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +155 -99
  228. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +107 -51
  229. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +109 -53
  230. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +107 -51
  231. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +107 -51
  232. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +107 -51
  233. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +107 -51
  234. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +107 -51
  235. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +107 -51
  236. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +117 -58
  237. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +113 -57
  238. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +107 -51
  239. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +107 -51
  240. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +107 -51
  241. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +107 -51
  242. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +110 -54
  243. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +107 -51
  244. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +110 -54
  245. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +110 -54
  246. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +110 -54
  247. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +110 -54
  248. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +107 -51
  249. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +107 -51
  250. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +113 -57
  251. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +110 -54
  252. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +110 -54
  253. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +110 -54
  254. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +110 -54
  255. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +116 -60
  256. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +110 -54
  257. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +110 -54
  258. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +119 -63
  259. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +110 -54
  260. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +110 -54
  261. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +113 -57
  262. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +113 -57
  263. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +113 -57
  264. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +107 -51
  265. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +113 -57
  266. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +107 -51
  267. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +110 -54
  268. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +110 -54
  269. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +107 -51
  270. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +110 -54
  271. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +110 -54
  272. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +107 -51
  273. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +107 -51
  274. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +107 -51
  275. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +110 -54
  276. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +110 -54
  277. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +110 -54
  278. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +110 -54
  279. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +107 -51
  280. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +113 -57
  281. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +110 -54
  282. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +110 -54
  283. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +110 -54
  284. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +125 -69
  285. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +113 -57
  286. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +128 -72
  287. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +110 -54
  288. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +107 -51
  289. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +110 -54
  290. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +227 -60
  291. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +107 -51
  292. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +113 -57
  293. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +107 -51
  294. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +110 -54
  295. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +107 -51
  296. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +107 -51
  297. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +107 -51
  298. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +107 -51
  299. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +113 -57
  300. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +113 -57
  301. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +107 -51
  302. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +113 -57
  303. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +107 -51
  304. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +107 -51
  305. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +107 -51
  306. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +107 -51
  307. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +107 -51
  308. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +107 -51
  309. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +107 -51
  310. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +107 -51
  311. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +107 -51
  312. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +110 -54
  313. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +113 -57
  314. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +107 -51
  315. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +110 -54
  316. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +107 -51
  317. nautobot/project-static/docs/user-guide/index.html +109 -53
  318. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +107 -51
  319. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +113 -57
  320. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +128 -72
  321. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +107 -51
  322. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +125 -69
  323. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +107 -51
  324. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +110 -54
  325. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +125 -69
  326. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +110 -54
  327. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +107 -51
  328. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +107 -51
  329. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +143 -100
  330. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +113 -57
  331. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +113 -57
  332. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +110 -54
  333. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +123 -67
  334. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +107 -51
  335. nautobot/project-static/docs/user-guide/platform-functionality/note.html +110 -54
  336. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +116 -60
  337. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +110 -54
  338. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +131 -75
  339. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +149 -93
  340. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +110 -54
  341. nautobot/project-static/docs/user-guide/platform-functionality/role.html +107 -51
  342. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +116 -60
  343. nautobot/project-static/docs/user-guide/platform-functionality/status.html +119 -63
  344. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +110 -54
  345. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +137 -81
  346. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +110 -54
  347. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +107 -51
  348. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +110 -54
  349. nautobot/project-static/js/forms.js +18 -11
  350. nautobot/tenancy/views.py +2 -6
  351. nautobot/virtualization/views.py +5 -9
  352. {nautobot-2.2.2.dist-info → nautobot-2.2.4.dist-info}/METADATA +4 -4
  353. {nautobot-2.2.2.dist-info → nautobot-2.2.4.dist-info}/RECORD +357 -348
  354. nautobot/extras/test_jobs/job_variables.py +0 -93
  355. nautobot/project-static/docs/assets/javascripts/bundle.bd41221c.min.js +0 -29
  356. nautobot/project-static/docs/assets/javascripts/bundle.bd41221c.min.js.map +0 -7
  357. nautobot/project-static/docs/assets/stylesheets/main.bcfcd587.min.css +0 -1
  358. nautobot/project-static/docs/assets/stylesheets/main.bcfcd587.min.css.map +0 -1
  359. {nautobot-2.2.2.dist-info → nautobot-2.2.4.dist-info}/LICENSE.txt +0 -0
  360. {nautobot-2.2.2.dist-info → nautobot-2.2.4.dist-info}/NOTICE +0 -0
  361. {nautobot-2.2.2.dist-info → nautobot-2.2.4.dist-info}/WHEEL +0 -0
  362. {nautobot-2.2.2.dist-info → nautobot-2.2.4.dist-info}/entry_points.txt +0 -0
@@ -14,8 +14,15 @@ import yaml
14
14
  from nautobot.circuits.choices import CircuitTerminationSideChoices
15
15
  from nautobot.circuits.models import Circuit, CircuitTermination, CircuitType, Provider
16
16
  from nautobot.core.templatetags.buttons import job_export_url, job_import_url
17
- from nautobot.core.testing import extract_page_body, ModelViewTestCase, post_data, ViewTestCases
18
- from nautobot.core.testing.utils import generate_random_device_asset_tag_of_specified_size
17
+ from nautobot.core.testing import (
18
+ extract_page_body,
19
+ ModelViewTestCase,
20
+ post_data,
21
+ ViewTestCases,
22
+ )
23
+ from nautobot.core.testing.utils import (
24
+ generate_random_device_asset_tag_of_specified_size,
25
+ )
19
26
  from nautobot.dcim.choices import (
20
27
  CableLengthUnitChoices,
21
28
  CableTypeChoices,
@@ -25,6 +32,7 @@ from nautobot.dcim.choices import (
25
32
  InterfaceModeChoices,
26
33
  InterfaceRedundancyGroupProtocolChoices,
27
34
  InterfaceTypeChoices,
35
+ LocationDataToContactActionChoices,
28
36
  PortTypeChoices,
29
37
  PowerFeedPhaseChoices,
30
38
  PowerFeedSupplyChoices,
@@ -89,10 +97,16 @@ from nautobot.dcim.models import (
89
97
  SoftwareVersion,
90
98
  VirtualChassis,
91
99
  )
92
- from nautobot.dcim.views import ConsoleConnectionsListView, InterfaceConnectionsListView, PowerConnectionsListView
100
+ from nautobot.dcim.views import (
101
+ ConsoleConnectionsListView,
102
+ InterfaceConnectionsListView,
103
+ PowerConnectionsListView,
104
+ )
93
105
  from nautobot.extras.choices import CustomFieldTypeChoices, RelationshipTypeChoices
94
106
  from nautobot.extras.models import (
95
107
  ConfigContextSchema,
108
+ Contact,
109
+ ContactAssociation,
96
110
  CustomField,
97
111
  CustomFieldChoice,
98
112
  ExternalIntegration,
@@ -102,6 +116,7 @@ from nautobot.extras.models import (
102
116
  SecretsGroup,
103
117
  Status,
104
118
  Tag,
119
+ Team,
105
120
  )
106
121
  from nautobot.ipam.choices import IPAddressTypeChoices
107
122
  from nautobot.ipam.models import IPAddress, Namespace, Prefix, VLAN, VLANGroup, VRF
@@ -128,7 +143,11 @@ def create_test_device(name):
128
143
  devicerole.content_types.add(device_ct)
129
144
  devicestatus = Status.objects.get_for_model(Device).first()
130
145
  device = Device.objects.create(
131
- name=name, location=location, device_type=devicetype, role=devicerole, status=devicestatus
146
+ name=name,
147
+ location=location,
148
+ device_type=devicetype,
149
+ role=devicerole,
150
+ status=devicestatus,
132
151
  )
133
152
 
134
153
  return device
@@ -161,7 +180,10 @@ class LocationTypeTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
161
180
  "name": "Intermediate 2",
162
181
  # "parent": lt1.pk, # TODO: Either overload how EditObjectViewTestCase finds an editable object or write a specific test case for this.
163
182
  "description": "Another intermediate type",
164
- "content_types": [ContentType.objects.get_for_model(Rack).pk, ContentType.objects.get_for_model(Device).pk],
183
+ "content_types": [
184
+ ContentType.objects.get_for_model(Rack).pk,
185
+ ContentType.objects.get_for_model(Device).pk,
186
+ ],
165
187
  "nestable": True,
166
188
  }
167
189
 
@@ -174,6 +196,8 @@ class LocationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
174
196
 
175
197
  @classmethod
176
198
  def setUpTestData(cls):
199
+ cls.contact_statuses = Status.objects.get_for_model(ContactAssociation)
200
+ cls.contact_roles = Role.objects.get_for_model(ContactAssociation)
177
201
  lt1 = LocationType.objects.get(name="Campus")
178
202
  lt2 = LocationType.objects.get(name="Building")
179
203
  lt3 = LocationType.objects.get(name="Floor")
@@ -186,7 +210,13 @@ class LocationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
186
210
  loc1 = Location.objects.create(name="Root 1", location_type=lt1, status=status)
187
211
  loc2 = Location.objects.create(name="Root 2", location_type=lt1, status=status, tenant=tenant)
188
212
  loc3 = Location.objects.create(name="Intermediate 1", location_type=lt2, parent=loc2, status=status)
189
- loc4 = Location.objects.create(name="Leaf 1", location_type=lt3, parent=loc3, status=status, description="Hi!")
213
+ loc4 = Location.objects.create(
214
+ name="Leaf 1",
215
+ location_type=lt3,
216
+ parent=loc3,
217
+ status=status,
218
+ description="Hi!",
219
+ )
190
220
  for loc in [loc1, loc2, loc3, loc4]:
191
221
  loc.validated_save()
192
222
 
@@ -222,7 +252,9 @@ class LocationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
222
252
  }
223
253
 
224
254
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
225
- def test_create_child_location_under_a_non_globally_unique_named_parent_location(self):
255
+ def test_create_child_location_under_a_non_globally_unique_named_parent_location(
256
+ self,
257
+ ):
226
258
  self.add_permissions("dcim.add_location")
227
259
  status = Status.objects.get_for_model(Location).first()
228
260
  region_type = LocationType.objects.create(name="Region")
@@ -252,6 +284,137 @@ class LocationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
252
284
  self.assertHttpStatus(self.client.post(**request), 302)
253
285
  self.assertEqual(Location.objects.get(name="Root 3").parent.pk, site_1.pk)
254
286
 
287
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
288
+ def test_migrate_location_data_from_location_assign(self):
289
+ self.add_permissions("dcim.change_location")
290
+ location = Location.objects.first()
291
+ location.contact_name = "Should be unique Contact Name"
292
+ location.contact_phone = "123123123"
293
+ location.contact_email = "helloword@example.com"
294
+ location.physical_address = "418 Brown Locks Barrettchester, NM 85792"
295
+ location.shipping_address = "53 blue Locks manchester, NY 12124"
296
+ similar_contact = Contact.objects.first()
297
+ role = self.contact_roles.first().pk
298
+ status = self.contact_statuses.first().pk
299
+ form_data = {
300
+ "action": LocationDataToContactActionChoices.ASSOCIATE_EXISTING_CONTACT,
301
+ "contact": similar_contact.pk,
302
+ "role": role,
303
+ "status": status,
304
+ }
305
+ request = {
306
+ "path": reverse("dcim:location_migrate_data_to_contact", kwargs={"pk": location.pk}),
307
+ "data": post_data(form_data),
308
+ }
309
+ # Assert permission checks are triggered
310
+ self.assertHttpStatus(self.client.post(**request), 200)
311
+ self.add_permissions("extras.add_contactassociation")
312
+ self.assertHttpStatus(self.client.post(**request), 302)
313
+ # assert ContactAssociation is created correctly
314
+ created_contact_association = ContactAssociation.objects.order_by("created").last()
315
+ self.assertEqual(created_contact_association.associated_object_id, location.pk)
316
+ self.assertEqual(created_contact_association.contact.pk, similar_contact.pk)
317
+ self.assertEqual(created_contact_association.role.pk, role)
318
+ self.assertEqual(created_contact_association.status.pk, status)
319
+
320
+ # assert location data is cleared out
321
+ location.refresh_from_db()
322
+ self.assertEqual(location.contact_name, "")
323
+ self.assertEqual(location.contact_phone, "")
324
+ self.assertEqual(location.contact_email, "")
325
+
326
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
327
+ def test_migrate_location_data_from_location_new_contact(self):
328
+ self.add_permissions("dcim.change_location")
329
+ location = Location.objects.first()
330
+ location.contact_name = "Should be unique Contact Name"
331
+ location.contact_phone = "123123123"
332
+ location.contact_email = "helloword@example.com"
333
+ location.physical_address = "418 Brown Locks Barrettchester, NM 85792"
334
+ location.shipping_address = "53 blue Locks manchester, NY 12124"
335
+ role = self.contact_roles.first().pk
336
+ status = self.contact_statuses.first().pk
337
+ form_data = {
338
+ "action": LocationDataToContactActionChoices.CREATE_AND_ASSIGN_NEW_CONTACT,
339
+ "name": "Should be unique Contact Name",
340
+ "phone": "123123123",
341
+ "email": "helloword@example.com",
342
+ "role": role,
343
+ "status": status,
344
+ }
345
+ request = {
346
+ "path": reverse("dcim:location_migrate_data_to_contact", kwargs={"pk": location.pk}),
347
+ "data": post_data(form_data),
348
+ }
349
+ # Assert permission checks are triggered
350
+ self.assertHttpStatus(self.client.post(**request), 200)
351
+ self.add_permissions("extras.add_contactassociation")
352
+ self.add_permissions("extras.add_contact")
353
+ self.assertHttpStatus(self.client.post(**request), 302)
354
+ # assert a new contact is created successfully
355
+ contact = Contact.objects.get(name="Should be unique Contact Name")
356
+ self.assertEqual(contact.name, form_data["name"])
357
+ self.assertEqual(contact.phone, form_data["phone"])
358
+ self.assertEqual(contact.email, form_data["email"])
359
+ # assert ContactAssociation is created correctly
360
+ created_contact_association = ContactAssociation.objects.order_by("created").last()
361
+ self.assertEqual(created_contact_association.associated_object_id, location.pk)
362
+ self.assertEqual(created_contact_association.contact.pk, contact.pk)
363
+ self.assertEqual(created_contact_association.role.pk, role)
364
+ self.assertEqual(created_contact_association.status.pk, status)
365
+
366
+ # assert location data is cleared out
367
+ location.refresh_from_db()
368
+ self.assertEqual(location.contact_name, "")
369
+ self.assertEqual(location.contact_phone, "")
370
+ self.assertEqual(location.contact_email, "")
371
+
372
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
373
+ def test_migrate_location_data_from_location_new_team(self):
374
+ self.add_permissions("dcim.change_location")
375
+ location = Location.objects.first()
376
+ location.contact_name = "Should be unique Team Name"
377
+ location.contact_phone = "123123123"
378
+ location.contact_email = "helloword@example.com"
379
+ location.physical_address = "418 Brown Locks Barrettchester, NM 85792"
380
+ location.shipping_address = "53 blue Locks manchester, NY 12124"
381
+ role = self.contact_roles.first().pk
382
+ status = self.contact_statuses.first().pk
383
+ form_data = {
384
+ "action": LocationDataToContactActionChoices.CREATE_AND_ASSIGN_NEW_TEAM,
385
+ "name": "Should be unique Team Name",
386
+ "phone": "123123123",
387
+ "email": "helloword@example.com",
388
+ "role": role,
389
+ "status": status,
390
+ }
391
+ request = {
392
+ "path": reverse("dcim:location_migrate_data_to_contact", kwargs={"pk": location.pk}),
393
+ "data": post_data(form_data),
394
+ }
395
+ # Assert permission checks are triggered
396
+ self.assertHttpStatus(self.client.post(**request), 200)
397
+ self.add_permissions("extras.add_contactassociation")
398
+ self.add_permissions("extras.add_team")
399
+ self.assertHttpStatus(self.client.post(**request), 302)
400
+ # assert a new team is created successfully
401
+ team = Team.objects.get(name="Should be unique Team Name")
402
+ self.assertEqual(team.name, form_data["name"])
403
+ self.assertEqual(team.phone, form_data["phone"])
404
+ self.assertEqual(team.email, form_data["email"])
405
+ # assert ContactAssociation is created correctly
406
+ created_contact_association = ContactAssociation.objects.order_by("created").last()
407
+ self.assertEqual(created_contact_association.associated_object_id, location.pk)
408
+ self.assertEqual(created_contact_association.team.pk, team.pk)
409
+ self.assertEqual(created_contact_association.role.pk, role)
410
+ self.assertEqual(created_contact_association.status.pk, status)
411
+
412
+ # assert location data is cleared out
413
+ location.refresh_from_db()
414
+ self.assertEqual(location.contact_name, "")
415
+ self.assertEqual(location.contact_phone, "")
416
+ self.assertEqual(location.contact_email, "")
417
+
255
418
 
256
419
  class RackGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
257
420
  model = RackGroup
@@ -340,7 +503,11 @@ class RackTestCase(ViewTestCases.PrimaryObjectViewTestCase):
340
503
  cls.cable_connected = cable_statuses.get(name="Connected")
341
504
 
342
505
  cls.custom_fields = (
343
- CustomField.objects.create(type=CustomFieldTypeChoices.TYPE_MULTISELECT, label="Rack Colors", default=[]),
506
+ CustomField.objects.create(
507
+ type=CustomFieldTypeChoices.TYPE_MULTISELECT,
508
+ label="Rack Colors",
509
+ default=[],
510
+ ),
344
511
  )
345
512
 
346
513
  CustomFieldChoice.objects.create(custom_field=cls.custom_fields[0], value="red")
@@ -389,7 +556,9 @@ class RackTestCase(ViewTestCases.PrimaryObjectViewTestCase):
389
556
 
390
557
  for rack in racks:
391
558
  RelationshipAssociation(
392
- relationship=cls.relationships[0], source=rack, destination=cls.locations[1]
559
+ relationship=cls.relationships[0],
560
+ source=rack,
561
+ destination=cls.locations[1],
393
562
  ).validated_save()
394
563
 
395
564
  cls.form_data = {
@@ -498,19 +667,31 @@ class RackTestCase(ViewTestCases.PrimaryObjectViewTestCase):
498
667
  poweroutlet1 = PowerOutlet.objects.create(device=devices[0], name="Power Outlet 11", power_port=powerport1)
499
668
 
500
669
  # connect power port to power feed (single-phase)
501
- cable1 = Cable(termination_a=powerfeed1, termination_b=powerport1, status=self.cable_connected)
670
+ cable1 = Cable(
671
+ termination_a=powerfeed1,
672
+ termination_b=powerport1,
673
+ status=self.cable_connected,
674
+ )
502
675
  cable1.save()
503
676
 
504
677
  # Create power port for 2nd device
505
678
  powerport2 = PowerPort.objects.create(device=devices[1], name="Power Port 12", allocated_draw=1200)
506
679
 
507
680
  # Connect power port to power outlet (dev1)
508
- cable2 = Cable(termination_a=powerport2, termination_b=poweroutlet1, status=self.cable_connected)
681
+ cable2 = Cable(
682
+ termination_a=powerport2,
683
+ termination_b=poweroutlet1,
684
+ status=self.cable_connected,
685
+ )
509
686
  cable2.save()
510
687
 
511
688
  # Create another power port for 2nd device and directly connect to the second PowerFeed.
512
689
  powerport3 = PowerPort.objects.create(device=devices[1], name="Power Port 13", allocated_draw=2400)
513
- cable3 = Cable(termination_a=powerfeed2, termination_b=powerport3, status=self.cable_connected)
690
+ cable3 = Cable(
691
+ termination_a=powerfeed2,
692
+ termination_b=powerport3,
693
+ status=self.cable_connected,
694
+ )
514
695
  cable3.save()
515
696
 
516
697
  # Test the view
@@ -654,7 +835,10 @@ class DeviceTypeTestCase(
654
835
  content,
655
836
  )
656
837
  self.assertInHTML('<input type="hidden" name="export_format" value="yaml">', content)
657
- self.assertInHTML('<button type="submit" class="btn btn-link">YAML format</button>', content)
838
+ self.assertInHTML(
839
+ '<button type="submit" class="btn btn-link" form="export_default">YAML format</button>',
840
+ content,
841
+ )
658
842
  self.assertInHTML('<button type="submit" class="btn btn-link">CSV format</button>', content)
659
843
 
660
844
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
@@ -1294,7 +1478,12 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
1294
1478
 
1295
1479
  rack_status = Status.objects.get_for_model(Rack).first()
1296
1480
  racks = (
1297
- Rack.objects.create(name="Rack 1", location=locations[0], rack_group=rack_group, status=rack_status),
1481
+ Rack.objects.create(
1482
+ name="Rack 1",
1483
+ location=locations[0],
1484
+ rack_group=rack_group,
1485
+ status=rack_status,
1486
+ ),
1298
1487
  Rack.objects.create(name="Rack 2", location=locations[1], status=rack_status),
1299
1488
  )
1300
1489
 
@@ -1333,7 +1522,11 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
1333
1522
  devicetypes[1].software_image_files.set(software_image_files[2:])
1334
1523
 
1335
1524
  cls.custom_fields = (
1336
- CustomField.objects.create(type=CustomFieldTypeChoices.TYPE_INTEGER, label="Crash Counter", default=0),
1525
+ CustomField.objects.create(
1526
+ type=CustomFieldTypeChoices.TYPE_INTEGER,
1527
+ label="Crash Counter",
1528
+ default=0,
1529
+ ),
1337
1530
  )
1338
1531
  cls.custom_fields[0].content_types.set([ContentType.objects.get_for_model(Device)])
1339
1532
 
@@ -1518,7 +1711,11 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
1518
1711
  def test_device_interface_assign_ipaddress(self):
1519
1712
  device = Device.objects.first()
1520
1713
  self.add_permissions(
1521
- "ipam.add_ipaddress", "extras.view_status", "ipam.view_namespace", "dcim.view_device", "dcim.view_interface"
1714
+ "ipam.add_ipaddress",
1715
+ "extras.view_status",
1716
+ "ipam.view_namespace",
1717
+ "dcim.view_device",
1718
+ "dcim.view_interface",
1522
1719
  )
1523
1720
  device_list_url = reverse("dcim:device_interfaces", args=(device.pk,))
1524
1721
  namespace = Namespace.objects.first()
@@ -1554,7 +1751,10 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
1554
1751
  self.assertHttpStatus(response, 200)
1555
1752
  self.interfaces[0].refresh_from_db()
1556
1753
  self.assertEqual(self.interfaces[0].ip_addresses.all().count(), 0)
1557
- self.assertIn(f"Interface with id &quot;{self.interfaces[0].pk}&quot; not found", response_body)
1754
+ self.assertIn(
1755
+ f"Interface with id &quot;{self.interfaces[0].pk}&quot; not found",
1756
+ response_body,
1757
+ )
1558
1758
 
1559
1759
  with self.subTest("Assert Cannnot assign IPAddress(Exsisting IP) without permission"):
1560
1760
  # Assert Assign Exsisting IPAddress
@@ -1563,7 +1763,10 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
1563
1763
  self.assertHttpStatus(response, 200)
1564
1764
  self.interfaces[1].refresh_from_db()
1565
1765
  self.assertEqual(self.interfaces[1].ip_addresses.all().count(), 0)
1566
- self.assertIn(f"Interface with id &quot;{self.interfaces[1].pk}&quot; not found", response_body)
1766
+ self.assertIn(
1767
+ f"Interface with id &quot;{self.interfaces[1].pk}&quot; not found",
1768
+ response_body,
1769
+ )
1567
1770
 
1568
1771
  self.add_permissions("dcim.change_interface", "ipam.view_ipaddress")
1569
1772
 
@@ -1571,7 +1774,8 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
1571
1774
  self.assertHttpStatus(self.client.post(**add_new_ip_request), 302)
1572
1775
  self.interfaces[0].refresh_from_db()
1573
1776
  self.assertEqual(
1574
- str(self.interfaces[0].ip_addresses.all().first().address), add_new_ip_form_data["address"]
1777
+ str(self.interfaces[0].ip_addresses.all().first().address),
1778
+ add_new_ip_form_data["address"],
1575
1779
  )
1576
1780
 
1577
1781
  with self.subTest("Assert Assign IPAddress"):
@@ -1681,7 +1885,8 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
1681
1885
  Assert that the local context passes schema validation via full_clean()
1682
1886
  """
1683
1887
  schema = ConfigContextSchema.objects.create(
1684
- name="Schema 1", data_schema={"type": "object", "properties": {"foo": {"type": "string"}}}
1888
+ name="Schema 1",
1889
+ data_schema={"type": "object", "properties": {"foo": {"type": "string"}}},
1685
1890
  )
1686
1891
  self.add_permissions("dcim.add_device")
1687
1892
 
@@ -1695,7 +1900,10 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
1695
1900
  "data": post_data(form_data),
1696
1901
  }
1697
1902
  self.assertHttpStatus(self.client.post(**request), 302)
1698
- self.assertEqual(self._get_queryset().get(name="Device X").local_config_context_schema.pk, schema.pk)
1903
+ self.assertEqual(
1904
+ self._get_queryset().get(name="Device X").local_config_context_schema.pk,
1905
+ schema.pk,
1906
+ )
1699
1907
 
1700
1908
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1701
1909
  def test_local_config_context_schema_validation_fails(self):
@@ -1705,7 +1913,8 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
1705
1913
  Assert that the local context fails schema validation via full_clean()
1706
1914
  """
1707
1915
  schema = ConfigContextSchema.objects.create(
1708
- name="Schema 1", data_schema={"type": "object", "properties": {"foo": {"type": "integer"}}}
1916
+ name="Schema 1",
1917
+ data_schema={"type": "object", "properties": {"foo": {"type": "integer"}}},
1709
1918
  )
1710
1919
  self.add_permissions("dcim.add_device")
1711
1920
 
@@ -1921,10 +2130,16 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
1921
2130
  Interface.objects.create(device=device, name="Interface 2", status=status_active),
1922
2131
  Interface.objects.create(device=device, name="Interface 3", status=status_active),
1923
2132
  Interface.objects.create(
1924
- device=device, name="LAG", status=status_active, type=InterfaceTypeChoices.TYPE_LAG
2133
+ device=device,
2134
+ name="LAG",
2135
+ status=status_active,
2136
+ type=InterfaceTypeChoices.TYPE_LAG,
1925
2137
  ),
1926
2138
  Interface.objects.create(
1927
- device=device, name="BRIDGE", status=status_active, type=InterfaceTypeChoices.TYPE_BRIDGE
2139
+ device=device,
2140
+ name="BRIDGE",
2141
+ status=status_active,
2142
+ type=InterfaceTypeChoices.TYPE_BRIDGE,
1928
2143
  ),
1929
2144
  )
1930
2145
  cls.lag_interface = interfaces[3]
@@ -1936,16 +2151,32 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
1936
2151
  vlan_group = VLANGroup.objects.first()
1937
2152
  vlans = (
1938
2153
  VLAN.objects.create(
1939
- vid=1, name="VLAN1", location=device.location, status=vlan_status, vlan_group=vlan_group
2154
+ vid=1,
2155
+ name="VLAN1",
2156
+ location=device.location,
2157
+ status=vlan_status,
2158
+ vlan_group=vlan_group,
1940
2159
  ),
1941
2160
  VLAN.objects.create(
1942
- vid=101, name="VLAN101", location=device.location, status=vlan_status, vlan_group=vlan_group
2161
+ vid=101,
2162
+ name="VLAN101",
2163
+ location=device.location,
2164
+ status=vlan_status,
2165
+ vlan_group=vlan_group,
1943
2166
  ),
1944
2167
  VLAN.objects.create(
1945
- vid=102, name="VLAN102", location=device.location, status=vlan_status, vlan_group=vlan_group
2168
+ vid=102,
2169
+ name="VLAN102",
2170
+ location=device.location,
2171
+ status=vlan_status,
2172
+ vlan_group=vlan_group,
1946
2173
  ),
1947
2174
  VLAN.objects.create(
1948
- vid=103, name="VLAN103", location=device.location, status=vlan_status, vlan_group=vlan_group
2175
+ vid=103,
2176
+ name="VLAN103",
2177
+ location=device.location,
2178
+ status=vlan_status,
2179
+ vlan_group=vlan_group,
1949
2180
  ),
1950
2181
  )
1951
2182
 
@@ -2432,22 +2663,37 @@ class CableTestCase(
2432
2663
  circuittype = CircuitType.objects.first()
2433
2664
  circuit_status = Status.objects.get_for_model(Circuit).first()
2434
2665
  circuit = Circuit.objects.create(
2435
- cid="Circuit 1", provider=provider, circuit_type=circuittype, status=circuit_status
2666
+ cid="Circuit 1",
2667
+ provider=provider,
2668
+ circuit_type=circuittype,
2669
+ status=circuit_status,
2436
2670
  )
2437
2671
 
2438
2672
  circuit_terminations = [
2439
2673
  CircuitTermination.objects.create(
2440
- circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_A, location=location
2674
+ circuit=circuit,
2675
+ term_side=CircuitTerminationSideChoices.SIDE_A,
2676
+ location=location,
2441
2677
  ),
2442
2678
  CircuitTermination.objects.create(
2443
- circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_Z, location=location
2679
+ circuit=circuit,
2680
+ term_side=CircuitTerminationSideChoices.SIDE_Z,
2681
+ location=location,
2444
2682
  ),
2445
2683
  ]
2446
2684
 
2447
2685
  status = Status.objects.get_for_model(Cable).get(name="Connected")
2448
2686
  cables = [
2449
- Cable.objects.create(termination_a=circuit_terminations[0], termination_b=interfaces[0], status=status),
2450
- Cable.objects.create(termination_a=circuit_terminations[1], termination_b=interfaces[1], status=status),
2687
+ Cable.objects.create(
2688
+ termination_a=circuit_terminations[0],
2689
+ termination_b=interfaces[0],
2690
+ status=status,
2691
+ ),
2692
+ Cable.objects.create(
2693
+ termination_a=circuit_terminations[1],
2694
+ termination_b=interfaces[1],
2695
+ status=status,
2696
+ ),
2451
2697
  ]
2452
2698
 
2453
2699
  request = {
@@ -2467,7 +2713,10 @@ class CableTestCase(
2467
2713
  cable_path_1 = CablePath.objects.filter(
2468
2714
  Q(origin_type=termination_ct, origin_id=circuit_terminations[0].pk)
2469
2715
  | Q(origin_type=interface_ct, origin_id=interfaces[0].pk)
2470
- | Q(destination_type=termination_ct, destination_id=circuit_terminations[0].pk)
2716
+ | Q(
2717
+ destination_type=termination_ct,
2718
+ destination_id=circuit_terminations[0].pk,
2719
+ )
2471
2720
  | Q(destination_type=interface_ct, destination_id=interfaces[0].pk)
2472
2721
  )
2473
2722
  # pylint: enable=unsupported-binary-operation
@@ -2478,7 +2727,10 @@ class CableTestCase(
2478
2727
  cable_path_2 = CablePath.objects.filter(
2479
2728
  Q(origin_type=termination_ct, origin_id=circuit_terminations[1].pk)
2480
2729
  | Q(origin_type=interface_ct, origin_id=interfaces[1].pk)
2481
- | Q(destination_type=termination_ct, destination_id=circuit_terminations[1].pk)
2730
+ | Q(
2731
+ destination_type=termination_ct,
2732
+ destination_id=circuit_terminations[1].pk,
2733
+ )
2482
2734
  | Q(destination_type=interface_ct, destination_id=interfaces[1].pk)
2483
2735
  )
2484
2736
  # pylint: enable=unsupported-binary-operation
@@ -2523,9 +2775,21 @@ class ConsoleConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
2523
2775
  )
2524
2776
  status_connected = Status.objects.get(name="Connected")
2525
2777
 
2526
- Cable.objects.create(termination_a=consoleports[0], termination_b=serverports[0], status=status_connected)
2527
- Cable.objects.create(termination_a=consoleports[1], termination_b=serverports[1], status=status_connected)
2528
- Cable.objects.create(termination_a=consoleports[2], termination_b=rearport, status=status_connected)
2778
+ Cable.objects.create(
2779
+ termination_a=consoleports[0],
2780
+ termination_b=serverports[0],
2781
+ status=status_connected,
2782
+ )
2783
+ Cable.objects.create(
2784
+ termination_a=consoleports[1],
2785
+ termination_b=serverports[1],
2786
+ status=status_connected,
2787
+ )
2788
+ Cable.objects.create(
2789
+ termination_a=consoleports[2],
2790
+ termination_b=rearport,
2791
+ status=status_connected,
2792
+ )
2529
2793
 
2530
2794
 
2531
2795
  class PowerConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
@@ -2572,10 +2836,22 @@ class PowerConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
2572
2836
 
2573
2837
  status_connected = Status.objects.get(name="Connected")
2574
2838
 
2575
- Cable.objects.create(termination_a=powerports[2], termination_b=powerfeed, status=status_connected)
2839
+ Cable.objects.create(
2840
+ termination_a=powerports[2],
2841
+ termination_b=powerfeed,
2842
+ status=status_connected,
2843
+ )
2576
2844
  # Creating a PowerOutlet with a PowerPort via the ORM does *not* automatically cable the two together. Bug?
2577
- Cable.objects.create(termination_a=powerports[0], termination_b=poweroutlets[0], status=status_connected)
2578
- Cable.objects.create(termination_a=powerports[1], termination_b=poweroutlets[1], status=status_connected)
2845
+ Cable.objects.create(
2846
+ termination_a=powerports[0],
2847
+ termination_b=poweroutlets[0],
2848
+ status=status_connected,
2849
+ )
2850
+ Cable.objects.create(
2851
+ termination_a=powerports[1],
2852
+ termination_b=poweroutlets[1],
2853
+ status=status_connected,
2854
+ )
2579
2855
 
2580
2856
 
2581
2857
  class InterfaceConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
@@ -2608,18 +2884,30 @@ class InterfaceConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
2608
2884
  interface_status = Status.objects.get_for_model(Interface).first()
2609
2885
  cls.interfaces = (
2610
2886
  Interface.objects.create(
2611
- device=device_1, name="Interface 1", type=InterfaceTypeChoices.TYPE_1GE_SFP, status=interface_status
2887
+ device=device_1,
2888
+ name="Interface 1",
2889
+ type=InterfaceTypeChoices.TYPE_1GE_SFP,
2890
+ status=interface_status,
2612
2891
  ),
2613
2892
  Interface.objects.create(
2614
- device=device_1, name="Interface 2", type=InterfaceTypeChoices.TYPE_1GE_SFP, status=interface_status
2893
+ device=device_1,
2894
+ name="Interface 2",
2895
+ type=InterfaceTypeChoices.TYPE_1GE_SFP,
2896
+ status=interface_status,
2615
2897
  ),
2616
2898
  Interface.objects.create(
2617
- device=device_1, name="Interface 3", type=InterfaceTypeChoices.TYPE_1GE_SFP, status=interface_status
2899
+ device=device_1,
2900
+ name="Interface 3",
2901
+ type=InterfaceTypeChoices.TYPE_1GE_SFP,
2902
+ status=interface_status,
2618
2903
  ),
2619
2904
  )
2620
2905
 
2621
2906
  cls.device_2_interface = Interface.objects.create(
2622
- device=device_2, name="Interface 1", type=InterfaceTypeChoices.TYPE_1GE_SFP, status=interface_status
2907
+ device=device_2,
2908
+ name="Interface 1",
2909
+ type=InterfaceTypeChoices.TYPE_1GE_SFP,
2910
+ status=interface_status,
2623
2911
  )
2624
2912
  rearport = RearPort.objects.create(device=device_2, type=PortTypeChoices.TYPE_8P8C)
2625
2913
 
@@ -2627,16 +2915,29 @@ class InterfaceConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
2627
2915
  circuittype = CircuitType.objects.first()
2628
2916
  circuit_status = Status.objects.get_for_model(Circuit).first()
2629
2917
  circuit = Circuit.objects.create(
2630
- cid="Circuit 1", provider=provider, circuit_type=circuittype, status=circuit_status
2918
+ cid="Circuit 1",
2919
+ provider=provider,
2920
+ circuit_type=circuittype,
2921
+ status=circuit_status,
2631
2922
  )
2632
2923
  circuittermination = CircuitTermination.objects.create(
2633
- circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_A, location=location
2924
+ circuit=circuit,
2925
+ term_side=CircuitTerminationSideChoices.SIDE_A,
2926
+ location=location,
2634
2927
  )
2635
2928
 
2636
2929
  connected = Status.objects.get(name="Connected")
2637
2930
 
2638
- Cable.objects.create(termination_a=cls.interfaces[0], termination_b=cls.device_2_interface, status=connected)
2639
- Cable.objects.create(termination_a=cls.interfaces[1], termination_b=circuittermination, status=connected)
2931
+ Cable.objects.create(
2932
+ termination_a=cls.interfaces[0],
2933
+ termination_b=cls.device_2_interface,
2934
+ status=connected,
2935
+ )
2936
+ Cable.objects.create(
2937
+ termination_a=cls.interfaces[1],
2938
+ termination_b=circuittermination,
2939
+ status=connected,
2940
+ )
2640
2941
  Cable.objects.create(termination_a=cls.interfaces[2], termination_b=rearport, status=connected)
2641
2942
 
2642
2943
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
@@ -2814,12 +3115,23 @@ class PowerFeedTestCase(ViewTestCases.PrimaryObjectViewTestCase):
2814
3115
  status_planned = statuses[0]
2815
3116
 
2816
3117
  powerfeed_1 = PowerFeed.objects.create(
2817
- name="Power Feed 1", power_panel=powerpanels[0], rack=racks[0], status=status_planned
3118
+ name="Power Feed 1",
3119
+ power_panel=powerpanels[0],
3120
+ rack=racks[0],
3121
+ status=status_planned,
2818
3122
  )
2819
3123
  powerfeed_2 = PowerFeed.objects.create(
2820
- name="Power Feed 2", power_panel=powerpanels[0], rack=racks[0], status=status_planned
3124
+ name="Power Feed 2",
3125
+ power_panel=powerpanels[0],
3126
+ rack=racks[0],
3127
+ status=status_planned,
3128
+ )
3129
+ PowerFeed.objects.create(
3130
+ name="Power Feed 3",
3131
+ power_panel=powerpanels[0],
3132
+ rack=racks[0],
3133
+ status=status_planned,
2821
3134
  )
2822
- PowerFeed.objects.create(name="Power Feed 3", power_panel=powerpanels[0], rack=racks[0], status=status_planned)
2823
3135
 
2824
3136
  # Assign power feeds for the tests later
2825
3137
  cls.powerfeeds = (powerfeed_1, powerfeed_2)
@@ -2872,7 +3184,9 @@ class PowerFeedTestCase(ViewTestCases.PrimaryObjectViewTestCase):
2872
3184
  powerfeed = self.powerfeeds[0]
2873
3185
 
2874
3186
  Cable.objects.create(
2875
- termination_a=powerport, termination_b=powerfeed, status=Status.objects.get(name="Connected")
3187
+ termination_a=powerport,
3188
+ termination_b=powerfeed,
3189
+ status=Status.objects.get(name="Connected"),
2876
3190
  )
2877
3191
 
2878
3192
  url = reverse("dcim:powerfeed", kwargs={"pk": powerfeed.pk})
@@ -2895,7 +3209,11 @@ class PathTraceViewTestCase(ModelViewTestCase):
2895
3209
  location_type = LocationType.objects.get(name="Campus")
2896
3210
  location = Location.objects.create(location_type=location_type, name="Location 1", status=active)
2897
3211
  device = Device.objects.create(
2898
- device_type=devicetype, role=devicerole, name="Device 1", location=location, status=active
3212
+ device_type=devicetype,
3213
+ role=devicerole,
3214
+ name="Device 1",
3215
+ location=location,
3216
+ status=active,
2899
3217
  )
2900
3218
  obj = RearPort.objects.create(device=device, name="Rear Port 1", type=PortTypeChoices.TYPE_8P8C)
2901
3219
  peer_obj = Interface.objects.create(device=device, name="eth0", status=active)
@@ -3026,7 +3344,10 @@ class InterfaceRedundancyGroupTestCase(ViewTestCases.PrimaryObjectViewTestCase):
3026
3344
 
3027
3345
  # Assign unconstrained permission
3028
3346
  self.add_permissions("dcim.add_interfaceredundancygroupassociation")
3029
- return_url = reverse("dcim:interfaceredundancygroup", kwargs={"pk": self.interface_redundancy_groups[0].pk})
3347
+ return_url = reverse(
3348
+ "dcim:interfaceredundancygroup",
3349
+ kwargs={"pk": self.interface_redundancy_groups[0].pk},
3350
+ )
3030
3351
  url = reverse("dcim:interfaceredundancygroupassociation_add")
3031
3352
  url = url + f"?interface_redundancy_group={self.interface_redundancy_groups[0].pk}&return_url={return_url}"
3032
3353
  self.assertHttpStatus(self.client.get(url), 200)