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
@@ -80,9 +80,10 @@ class GitTest(TransactionTestCase):
80
80
  status=status,
81
81
  )
82
82
 
83
+ self.repo_slug = "test_git_repo"
83
84
  self.repo = GitRepository(
84
85
  name="Test Git Repository",
85
- slug="test_git_repo",
86
+ slug=self.repo_slug,
86
87
  remote_url="http://localhost/git.git",
87
88
  # Provide everything we know we can provide
88
89
  provided_contents=[entry.content_identifier for entry in get_datasource_contents("extras.gitrepository")],
@@ -118,22 +119,24 @@ class GitTest(TransactionTestCase):
118
119
  }
119
120
 
120
121
  def tearDown(self):
121
- if f"{self.repo.slug}.jobs" in sys.modules:
122
- del sys.modules[f"{self.repo.slug}.jobs"]
123
- if f"{self.repo.slug}" in sys.modules:
124
- del sys.modules[f"{self.repo.slug}"]
122
+ if f"{self.repo_slug}.jobs" in sys.modules:
123
+ del sys.modules[f"{self.repo_slug}.jobs"]
124
+ if f"{self.repo_slug}" in sys.modules:
125
+ del sys.modules[f"{self.repo_slug}"]
126
+ if self.repo is not None:
127
+ self.repo.delete()
125
128
  super().tearDown()
126
129
 
127
130
  def populate_repo(self, path, url, *args, **kwargs):
128
- os.makedirs(path)
131
+ os.makedirs(path, exist_ok=True)
129
132
 
130
- os.makedirs(os.path.join(path, "config_contexts"))
131
- os.makedirs(os.path.join(path, "config_contexts", "devices"))
132
- os.makedirs(os.path.join(path, "config_contexts", "locations"))
133
- os.makedirs(os.path.join(path, "config_context_schemas"))
134
- os.makedirs(os.path.join(path, "export_templates", "dcim", "device"))
135
- os.makedirs(os.path.join(path, "export_templates", "ipam", "vlan"))
136
- os.makedirs(os.path.join(path, "jobs"))
133
+ os.makedirs(os.path.join(path, "config_contexts"), exist_ok=True)
134
+ os.makedirs(os.path.join(path, "config_contexts", "devices"), exist_ok=True)
135
+ os.makedirs(os.path.join(path, "config_contexts", "locations"), exist_ok=True)
136
+ os.makedirs(os.path.join(path, "config_context_schemas"), exist_ok=True)
137
+ os.makedirs(os.path.join(path, "export_templates", "dcim", "device"), exist_ok=True)
138
+ os.makedirs(os.path.join(path, "export_templates", "ipam", "vlan"), exist_ok=True)
139
+ os.makedirs(os.path.join(path, "jobs"), exist_ok=True)
137
140
 
138
141
  with open(os.path.join(path, "__init__.py"), "w") as fd:
139
142
  # Required for job importing
@@ -188,16 +191,19 @@ class GitTest(TransactionTestCase):
188
191
  return mock.DEFAULT
189
192
 
190
193
  def empty_repo(self, path, url, *args, **kwargs):
191
- os.remove(os.path.join(path, "__init__.py"))
192
- os.remove(os.path.join(path, "config_contexts", "context.yaml"))
193
- os.remove(os.path.join(path, "config_contexts", "locations", f"{self.location.name}.json"))
194
- os.remove(os.path.join(path, "config_contexts", "devices", f"{self.device.name}.json"))
195
- os.remove(os.path.join(path, "config_context_schemas", "schema-1.yaml"))
196
- os.remove(os.path.join(path, "export_templates", "dcim", "device", "template.j2"))
197
- os.remove(os.path.join(path, "export_templates", "dcim", "device", "template2.html"))
198
- os.remove(os.path.join(path, "export_templates", "ipam", "vlan", "template.j2"))
199
- os.remove(os.path.join(path, "jobs", "__init__.py"))
200
- os.remove(os.path.join(path, "jobs", "my_job.py"))
194
+ try:
195
+ os.remove(os.path.join(path, "__init__.py"))
196
+ os.remove(os.path.join(path, "config_contexts", "context.yaml"))
197
+ os.remove(os.path.join(path, "config_contexts", "locations", f"{self.location.name}.json"))
198
+ os.remove(os.path.join(path, "config_contexts", "devices", f"{self.device.name}.json"))
199
+ os.remove(os.path.join(path, "config_context_schemas", "schema-1.yaml"))
200
+ os.remove(os.path.join(path, "export_templates", "dcim", "device", "template.j2"))
201
+ os.remove(os.path.join(path, "export_templates", "dcim", "device", "template2.html"))
202
+ os.remove(os.path.join(path, "export_templates", "ipam", "vlan", "template.j2"))
203
+ os.remove(os.path.join(path, "jobs", "__init__.py"))
204
+ os.remove(os.path.join(path, "jobs", "my_job.py"))
205
+ except FileNotFoundError:
206
+ pass
201
207
  return mock.DEFAULT
202
208
 
203
209
  def assert_repo_slug_valid_python_package_name(self):
@@ -291,15 +297,14 @@ class GitTest(TransactionTestCase):
291
297
  self.assertIsNotNone(export_template_vlan)
292
298
 
293
299
  def assert_job_exists(self, installed=True):
294
- """Helper function to assert JobModel and registerd Job exist."""
300
+ """Helper function to assert JobModel and registered Job exist."""
295
301
  # Is it registered correctly in the database?
296
- job_model = Job.objects.get(name="MyJob", module_name=f"{self.repo.slug}.jobs.my_job", job_class_name="MyJob")
302
+ job_model = Job.objects.get(name="MyJob", module_name=f"{self.repo_slug}.jobs.my_job", job_class_name="MyJob")
297
303
  self.assertIsNotNone(job_model)
298
304
  if installed:
299
305
  self.assertTrue(job_model.installed)
300
306
  # Is the in-memory code accessible?
301
307
  self.assertIsNotNone(job_model.job_class)
302
- # Is it registered properly with Celery?
303
308
  self.assertIsNotNone(job_model.job_task)
304
309
  else:
305
310
  self.assertFalse(job_model.installed)
@@ -333,10 +338,7 @@ class GitTest(TransactionTestCase):
333
338
  self.assertEqual(
334
339
  job_result.status,
335
340
  JobResultStatusChoices.STATUS_FAILURE,
336
- (
337
- job_result.result,
338
- list(job_result.job_log_entries.filter(log_level="error").values_list("message", flat=True)),
339
- ),
341
+ (job_result.result, list(job_result.job_log_entries.values_list("message", "log_object"))),
340
342
  )
341
343
  self.repo.refresh_from_db()
342
344
  self.assertEqual(self.repo.current_head, self.COMMIT_HEXSHA, job_result.result)
@@ -344,7 +346,13 @@ class GitTest(TransactionTestCase):
344
346
 
345
347
  log_entries = JobLogEntry.objects.filter(job_result=job_result)
346
348
  failure_logs = log_entries.filter(log_level=LogLevelChoices.LOG_ERROR)
347
- failure_logs.get(grouping="jobs", message__contains="Error in loading Jobs from Git repository: ")
349
+ try:
350
+ failure_logs.get(grouping="jobs", message__contains="No `jobs` submodule found")
351
+ except JobLogEntry.DoesNotExist:
352
+ for log in log_entries:
353
+ print(log.message)
354
+ print(job_result.traceback)
355
+ raise
348
356
 
349
357
  def test_pull_git_repository_and_refresh_data_with_secrets(self, MockGitRepo):
350
358
  """
@@ -406,10 +414,7 @@ class GitTest(TransactionTestCase):
406
414
  self.assertEqual(
407
415
  job_result.status,
408
416
  JobResultStatusChoices.STATUS_SUCCESS,
409
- (
410
- job_result.result,
411
- list(job_result.job_log_entries.filter(log_level="error").values_list("message", flat=True)),
412
- ),
417
+ (job_result.traceback, list(job_result.job_log_entries.values_list("message", flat=True))),
413
418
  )
414
419
  self.repo.refresh_from_db()
415
420
  MockGitRepo.assert_called_with(
@@ -433,10 +438,7 @@ class GitTest(TransactionTestCase):
433
438
  self.assertEqual(
434
439
  job_result.status,
435
440
  JobResultStatusChoices.STATUS_SUCCESS,
436
- (
437
- job_result.result,
438
- list(job_result.job_log_entries.filter(log_level="error").values_list("message", flat=True)),
439
- ),
441
+ (job_result.traceback, list(job_result.job_log_entries.values_list("message", flat=True))),
440
442
  )
441
443
 
442
444
  # Make sure explicit ConfigContext was successfully loaded from file
@@ -475,10 +477,7 @@ class GitTest(TransactionTestCase):
475
477
  self.assertEqual(
476
478
  job_result.status,
477
479
  JobResultStatusChoices.STATUS_SUCCESS,
478
- (
479
- job_result.result,
480
- list(job_result.job_log_entries.filter(log_level="error").values_list("message", flat=True)),
481
- ),
480
+ (job_result.traceback, list(job_result.job_log_entries.values_list("message", flat=True))),
482
481
  )
483
482
 
484
483
  # Verify that objects have been removed from the database
@@ -515,16 +514,16 @@ class GitTest(TransactionTestCase):
515
514
  with self.settings(GIT_ROOT=tempdir):
516
515
 
517
516
  def populate_repo(path, url):
518
- os.makedirs(path)
519
- os.makedirs(os.path.join(path, "config_contexts"))
520
- os.makedirs(os.path.join(path, "config_contexts", "devices"))
521
- os.makedirs(os.path.join(path, "config_context_schemas"))
522
- os.makedirs(os.path.join(path, "export_templates", "nosuchapp", "device"))
523
- os.makedirs(os.path.join(path, "export_templates", "dcim", "nosuchmodel"))
524
- os.makedirs(os.path.join(path, "jobs"))
517
+ os.makedirs(path, exist_ok=True)
518
+ os.makedirs(os.path.join(path, "config_contexts"), exist_ok=True)
519
+ os.makedirs(os.path.join(path, "config_contexts", "devices"), exist_ok=True)
520
+ os.makedirs(os.path.join(path, "config_context_schemas"), exist_ok=True)
521
+ os.makedirs(os.path.join(path, "export_templates", "nosuchapp", "device"), exist_ok=True)
522
+ os.makedirs(os.path.join(path, "export_templates", "dcim", "nosuchmodel"), exist_ok=True)
523
+ os.makedirs(os.path.join(path, "jobs"), exist_ok=True)
525
524
  # Incorrect directories
526
- os.makedirs(os.path.join(path, "devices"))
527
- os.makedirs(os.path.join(path, "dcim"))
525
+ os.makedirs(os.path.join(path, "devices"), exist_ok=True)
526
+ os.makedirs(os.path.join(path, "dcim"), exist_ok=True)
528
527
  with open(os.path.join(path, "__init__.py"), "w") as fd:
529
528
  pass
530
529
  # Malformed JSON
@@ -563,6 +562,7 @@ class GitTest(TransactionTestCase):
563
562
 
564
563
  # Run the Git operation and refresh the object from the DB
565
564
  job_model = GitRepositorySync().job_model
565
+ self.assertIsNotNone(job_model)
566
566
  job_result = run_job_for_testing(
567
567
  job=job_model,
568
568
  repository=self.repo.pk,
@@ -580,54 +580,65 @@ class GitTest(TransactionTestCase):
580
580
  warning_logs = log_entries.filter(log_level=LogLevelChoices.LOG_WARNING)
581
581
  failure_logs = log_entries.filter(log_level=LogLevelChoices.LOG_ERROR)
582
582
 
583
- warning_logs.get(
584
- grouping="config contexts", message__contains='Found "devices" directory in the repository root'
585
- )
586
- warning_logs.get(
587
- grouping="export templates", message__contains='Found "dcim" directory in the repository root'
588
- )
589
- warning_logs.get(
590
- grouping="export templates",
591
- message__contains="Skipping `dcim.nosuchmodel` as it isn't a known content type",
592
- )
593
- warning_logs.get(
594
- grouping="export templates",
595
- message__contains="Skipping `nosuchapp.device` as it isn't a known content type",
596
- )
597
-
598
- failure_logs.get(
599
- grouping="config context schemas",
600
- message__contains="Error in loading config context schema data from `schema-1.yaml`",
601
- )
602
- failure_logs.get(
603
- grouping="config context schemas",
604
- message__contains="Error in loading config context schema data from `schema-2.yaml`: "
605
- "data is missing the required `_metadata` key",
606
- )
607
- failure_logs.get(
608
- grouping="config contexts",
609
- message__contains="Error in loading config context data from `context.json`",
610
- )
611
- failure_logs.get(
612
- grouping="config contexts",
613
- message__contains="Error in loading config context data from `context2.json`: "
614
- "data is missing the required `_metadata` key",
615
- )
616
- failure_logs.get(
617
- grouping="config contexts",
618
- message__contains="Error in loading config context data from `context3.json`: "
619
- "data `_metadata` is missing the required `name` key",
620
- )
621
- failure_logs.get(
622
- grouping="local config contexts",
623
- message__contains="Error in loading local config context from `devices/nosuchdevice.json`: "
624
- "record not found",
625
- )
626
- failure_logs.get(
627
- grouping="jobs",
628
- # The specific exception message differs between Python versions
629
- message__contains="Error in loading Jobs from Git repository: ",
630
- )
583
+ try:
584
+ warning_logs.get(
585
+ grouping="config contexts", message__contains='Found "devices" directory in the repository root'
586
+ )
587
+ warning_logs.get(
588
+ grouping="export templates", message__contains='Found "dcim" directory in the repository root'
589
+ )
590
+ warning_logs.get(
591
+ grouping="export templates",
592
+ message__contains="Skipping `dcim.nosuchmodel` as it isn't a known content type",
593
+ )
594
+ warning_logs.get(
595
+ grouping="export templates",
596
+ message__contains="Skipping `nosuchapp.device` as it isn't a known content type",
597
+ )
598
+ except JobLogEntry.DoesNotExist:
599
+ for log in log_entries:
600
+ print(log.message)
601
+ print(job_result.traceback)
602
+ raise
603
+
604
+ try:
605
+ failure_logs.get(
606
+ grouping="config context schemas",
607
+ message__contains="Error in loading config context schema data from `schema-1.yaml`",
608
+ )
609
+ failure_logs.get(
610
+ grouping="config context schemas",
611
+ message__contains="Error in loading config context schema data from `schema-2.yaml`: "
612
+ "data is missing the required `_metadata` key",
613
+ )
614
+ failure_logs.get(
615
+ grouping="config contexts",
616
+ message__contains="Error in loading config context data from `context.json`",
617
+ )
618
+ failure_logs.get(
619
+ grouping="config contexts",
620
+ message__contains="Error in loading config context data from `context2.json`: "
621
+ "data is missing the required `_metadata` key",
622
+ )
623
+ failure_logs.get(
624
+ grouping="config contexts",
625
+ message__contains="Error in loading config context data from `context3.json`: "
626
+ "data `_metadata` is missing the required `name` key",
627
+ )
628
+ failure_logs.get(
629
+ grouping="local config contexts",
630
+ message__contains="Error in loading local config context from `devices/nosuchdevice.json`: "
631
+ "record not found",
632
+ )
633
+ failure_logs.get(
634
+ grouping="jobs",
635
+ message__contains="Error in loading Jobs from Git repository: ",
636
+ )
637
+ except (AssertionError, JobLogEntry.DoesNotExist):
638
+ for log in log_entries:
639
+ print(log.message)
640
+ print(job_result.traceback)
641
+ raise
631
642
 
632
643
  def test_delete_git_repository_cleanup(self, MockGitRepo):
633
644
  """
@@ -637,12 +648,12 @@ class GitTest(TransactionTestCase):
637
648
  with self.settings(GIT_ROOT=tempdir):
638
649
 
639
650
  def populate_repo(path, url):
640
- os.makedirs(path)
641
- os.makedirs(os.path.join(path, "config_contexts"))
642
- os.makedirs(os.path.join(path, "config_contexts", "devices"))
643
- os.makedirs(os.path.join(path, "config_context_schemas"))
644
- os.makedirs(os.path.join(path, "export_templates", "dcim", "device"))
645
- os.makedirs(os.path.join(path, "jobs"))
651
+ os.makedirs(path, exist_ok=True)
652
+ os.makedirs(os.path.join(path, "config_contexts"), exist_ok=True)
653
+ os.makedirs(os.path.join(path, "config_contexts", "devices"), exist_ok=True)
654
+ os.makedirs(os.path.join(path, "config_context_schemas"), exist_ok=True)
655
+ os.makedirs(os.path.join(path, "export_templates", "dcim", "device"), exist_ok=True)
656
+ os.makedirs(os.path.join(path, "jobs"), exist_ok=True)
646
657
  with open(os.path.join(path, "__init__.py"), "w") as fd:
647
658
  pass
648
659
  with open(os.path.join(path, "config_contexts", "context.yaml"), "w") as fd:
@@ -701,10 +712,7 @@ class GitTest(TransactionTestCase):
701
712
  self.assertEqual(
702
713
  job_result.status,
703
714
  JobResultStatusChoices.STATUS_SUCCESS,
704
- (
705
- job_result.result,
706
- list(job_result.job_log_entries.filter(log_level="error").values_list("message", flat=True)),
707
- ),
715
+ (job_result.traceback, list(job_result.job_log_entries.values_list("message", flat=True))),
708
716
  )
709
717
 
710
718
  # Make sure ConfigContext was successfully loaded from file
@@ -735,7 +743,9 @@ class GitTest(TransactionTestCase):
735
743
  self.assert_job_exists()
736
744
 
737
745
  # Now delete the GitRepository
746
+ repo_pk = self.repo.pk
738
747
  self.repo.delete()
748
+ self.repo = None
739
749
 
740
750
  with self.subTest("Assert Deleted GitRepo do not create a never ending JobResult record"):
741
751
  # Bug fix test for https://github.com/nautobot/nautobot/issues/5121
@@ -743,19 +753,19 @@ class GitTest(TransactionTestCase):
743
753
 
744
754
  with self.assertRaises(ConfigContext.DoesNotExist):
745
755
  ConfigContext.objects.get(
746
- owner_object_id=self.repo.pk,
756
+ owner_object_id=repo_pk,
747
757
  owner_content_type=ContentType.objects.get_for_model(GitRepository),
748
758
  )
749
759
 
750
760
  with self.assertRaises(ConfigContextSchema.DoesNotExist):
751
761
  ConfigContextSchema.objects.get(
752
- owner_object_id=self.repo.pk,
762
+ owner_object_id=repo_pk,
753
763
  owner_content_type=ContentType.objects.get_for_model(GitRepository),
754
764
  )
755
765
 
756
766
  with self.assertRaises(ExportTemplate.DoesNotExist):
757
767
  ExportTemplate.objects.get(
758
- owner_object_id=self.repo.pk,
768
+ owner_object_id=repo_pk,
759
769
  owner_content_type=ContentType.objects.get_for_model(GitRepository),
760
770
  )
761
771
 
@@ -774,6 +784,7 @@ class GitTest(TransactionTestCase):
774
784
  return mock.DEFAULT
775
785
 
776
786
  MockGitRepo.side_effect = create_empty_repo
787
+ MockGitRepo.return_value.checkout.return_value = (self.COMMIT_HEXSHA, False)
777
788
 
778
789
  self.mock_request.id = uuid.uuid4()
779
790
 
@@ -787,13 +798,9 @@ class GitTest(TransactionTestCase):
787
798
  self.assertEqual(
788
799
  job_result.status,
789
800
  JobResultStatusChoices.STATUS_SUCCESS,
790
- (
791
- job_result.result,
792
- list(job_result.job_log_entries.filter(log_level="error").values_list("message", flat=True)),
793
- ),
801
+ (job_result.traceback, list(job_result.job_log_entries.values_list("message", flat=True))),
794
802
  )
795
803
 
796
- MockGitRepo.return_value.checkout.assert_not_called()
797
804
  MockGitRepo.assert_called_with(
798
805
  os.path.join(tempdir, self.repo.slug),
799
806
  self.repo.remote_url,
@@ -3,26 +3,32 @@ from django.test import TestCase
3
3
  from netaddr import IPAddress, IPNetwork
4
4
 
5
5
  from nautobot.dcim.models import Device
6
- from nautobot.extras.models import Role
7
- from nautobot.extras.test_jobs.job_variables import (
8
- BooleanVarJob,
9
- ChoiceVarJob,
10
- FileVarJob,
11
- IntegerVarJob,
12
- IPAddressVarJob,
13
- IPAddressWithMaskVarJob,
14
- IPNetworkVarJob,
15
- JSONVarJob,
16
- MultiChoiceVarJob,
17
- MultiObjectVarJob,
18
- ObjectVarJob,
19
- StringVarJob,
20
- TextVarJob,
6
+ from nautobot.extras.jobs import (
7
+ BooleanVar,
8
+ ChoiceVar,
9
+ FileVar,
10
+ IntegerVar,
11
+ IPAddressVar,
12
+ IPAddressWithMaskVar,
13
+ IPNetworkVar,
14
+ Job,
15
+ JSONVar,
16
+ MultiChoiceVar,
17
+ MultiObjectVar,
18
+ ObjectVar,
19
+ StringVar,
20
+ TextVar,
21
21
  )
22
+ from nautobot.extras.models import Role
23
+
24
+ CHOICES = (("ff0000", "Red"), ("00ff00", "Green"), ("0000ff", "Blue"))
22
25
 
23
26
 
24
27
  class JobVariablesTest(TestCase):
25
28
  def test_stringvar(self):
29
+ class StringVarJob(Job):
30
+ var1 = StringVar(min_length=3, max_length=3, regex=r"[a-z]+")
31
+
26
32
  # Validate min_length enforcement
27
33
  data = {"var1": "xx"}
28
34
  form = StringVarJob().as_form(data)
@@ -48,6 +54,9 @@ class JobVariablesTest(TestCase):
48
54
  self.assertEqual(form.cleaned_data["var1"], data["var1"])
49
55
 
50
56
  def test_textvar(self):
57
+ class TextVarJob(Job):
58
+ var1 = TextVar()
59
+
51
60
  # Validate valid data
52
61
  data = {"var1": "This is a test string"}
53
62
  form = TextVarJob().as_form(data)
@@ -55,6 +64,9 @@ class JobVariablesTest(TestCase):
55
64
  self.assertEqual(form.cleaned_data["var1"], data["var1"])
56
65
 
57
66
  def test_integervar(self):
67
+ class IntegerVarJob(Job):
68
+ var1 = IntegerVar(min_value=5, max_value=10)
69
+
58
70
  # Validate min_value enforcement
59
71
  data = {"var1": 4}
60
72
  form = IntegerVarJob().as_form(data)
@@ -74,6 +86,9 @@ class JobVariablesTest(TestCase):
74
86
  self.assertEqual(form.cleaned_data["var1"], data["var1"])
75
87
 
76
88
  def test_booleanvar(self):
89
+ class BooleanVarJob(Job):
90
+ var1 = BooleanVar()
91
+
77
92
  # Validate True
78
93
  data = {"var1": True}
79
94
  form = BooleanVarJob().as_form(data)
@@ -87,6 +102,9 @@ class JobVariablesTest(TestCase):
87
102
  self.assertEqual(form.cleaned_data["var1"], False)
88
103
 
89
104
  def test_choicevar(self):
105
+ class ChoiceVarJob(Job):
106
+ var1 = ChoiceVar(choices=CHOICES)
107
+
90
108
  # Validate valid choice
91
109
  data = {"var1": "ff0000"}
92
110
  form = ChoiceVarJob().as_form(data)
@@ -100,6 +118,9 @@ class JobVariablesTest(TestCase):
100
118
  self.assertIn("var1", form.errors)
101
119
 
102
120
  def test_multichoicevar(self):
121
+ class MultiChoiceVarJob(Job):
122
+ var1 = MultiChoiceVar(choices=CHOICES)
123
+
103
124
  # Validate single choice
104
125
  data = {"var1": ["ff0000"]}
105
126
  form = MultiChoiceVarJob().as_form(data)
@@ -119,6 +140,9 @@ class JobVariablesTest(TestCase):
119
140
  self.assertIn("var1", form.errors)
120
141
 
121
142
  def test_objectvar(self):
143
+ class ObjectVarJob(Job):
144
+ var1 = ObjectVar(model=Role)
145
+
122
146
  # Validate valid data
123
147
  data = {"var1": Role.objects.get_for_model(Device).first().pk}
124
148
  form = ObjectVarJob().as_form(data)
@@ -126,6 +150,9 @@ class JobVariablesTest(TestCase):
126
150
  self.assertEqual(form.cleaned_data["var1"].pk, data["var1"])
127
151
 
128
152
  def test_multiobjectvar(self):
153
+ class MultiObjectVarJob(Job):
154
+ var1 = MultiObjectVar(model=Role)
155
+
129
156
  # Validate valid data
130
157
  data = {"var1": [role.pk for role in Role.objects.all()[:3]]}
131
158
  form = MultiObjectVarJob().as_form(data)
@@ -135,6 +162,9 @@ class JobVariablesTest(TestCase):
135
162
  self.assertEqual(form.cleaned_data["var1"][2].pk, data["var1"][2])
136
163
 
137
164
  def test_filevar(self):
165
+ class FileVarJob(Job):
166
+ var1 = FileVar()
167
+
138
168
  # Test file
139
169
  testfile = SimpleUploadedFile(name="test_file.txt", content=b"This is an test file for testing")
140
170
 
@@ -145,6 +175,9 @@ class JobVariablesTest(TestCase):
145
175
  self.assertEqual(form.cleaned_data["var1"], testfile)
146
176
 
147
177
  def test_ipaddressvar(self):
178
+ class IPAddressVarJob(Job):
179
+ var1 = IPAddressVar()
180
+
148
181
  # Validate IP network enforcement
149
182
  data = {"var1": "1.2.3"}
150
183
  form = IPAddressVarJob().as_form(data)
@@ -164,6 +197,9 @@ class JobVariablesTest(TestCase):
164
197
  self.assertEqual(form.cleaned_data["var1"], IPAddress(data["var1"]))
165
198
 
166
199
  def test_ipaddresswithmaskvar(self):
200
+ class IPAddressWithMaskVarJob(Job):
201
+ var1 = IPAddressWithMaskVar()
202
+
167
203
  # Validate IP network enforcement
168
204
  data = {"var1": "1.2.3"}
169
205
  form = IPAddressWithMaskVarJob().as_form(data)
@@ -183,6 +219,9 @@ class JobVariablesTest(TestCase):
183
219
  self.assertEqual(form.cleaned_data["var1"], IPNetwork(data["var1"]))
184
220
 
185
221
  def test_ipnetworkvar(self):
222
+ class IPNetworkVarJob(Job):
223
+ var1 = IPNetworkVar()
224
+
186
225
  # Validate IP network enforcement
187
226
  data = {"var1": "1.2.3"}
188
227
  form = IPNetworkVarJob().as_form(data)
@@ -202,6 +241,9 @@ class JobVariablesTest(TestCase):
202
241
  self.assertEqual(form.cleaned_data["var1"], IPNetwork(data["var1"]))
203
242
 
204
243
  def test_jsonvar(self):
244
+ class JSONVarJob(Job):
245
+ var1 = JSONVar()
246
+
205
247
  # Valid JSON value as dictionary
206
248
  data = {"var1": {"key1": "value1"}}
207
249
  form = JSONVarJob().as_form(data)