nautobot 2.2.4__py3-none-any.whl → 2.2.6__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 (365) hide show
  1. nautobot/apps/api.py +2 -0
  2. nautobot/apps/models.py +2 -0
  3. nautobot/circuits/forms.py +15 -0
  4. nautobot/circuits/navigation.py +9 -1
  5. nautobot/circuits/views.py +2 -0
  6. nautobot/core/api/fields.py +13 -0
  7. nautobot/core/api/serializers.py +7 -1
  8. nautobot/core/filters.py +11 -0
  9. nautobot/core/management/commands/generate_test_data.py +128 -158
  10. nautobot/core/models/fields.py +15 -0
  11. nautobot/core/settings.yaml +3 -3
  12. nautobot/core/testing/filters.py +24 -1
  13. nautobot/core/testing/views.py +13 -1
  14. nautobot/core/tests/test_utils.py +48 -1
  15. nautobot/core/utils/git.py +121 -49
  16. nautobot/core/utils/module_loading.py +10 -2
  17. nautobot/core/views/utils.py +18 -1
  18. nautobot/dcim/factory.py +1 -1
  19. nautobot/dcim/filters/__init__.py +1 -1
  20. nautobot/dcim/forms.py +23 -4
  21. nautobot/dcim/tables/devicetypes.py +15 -4
  22. nautobot/dcim/tests/test_models.py +2 -0
  23. nautobot/dcim/tests/test_views.py +84 -0
  24. nautobot/dcim/views.py +3 -0
  25. nautobot/extras/api/views.py +2 -2
  26. nautobot/extras/context_managers.py +3 -0
  27. nautobot/extras/datasources/git.py +133 -135
  28. nautobot/extras/datasources/utils.py +3 -0
  29. nautobot/extras/filters/__init__.py +16 -1
  30. nautobot/extras/forms/forms.py +49 -3
  31. nautobot/extras/forms/mixins.py +0 -6
  32. nautobot/extras/jobs.py +9 -1
  33. nautobot/extras/migrations/0107_laxurlfield.py +28 -0
  34. nautobot/extras/migrations/0108_jobbutton_enabled.py +17 -0
  35. nautobot/extras/models/datasources.py +6 -4
  36. nautobot/extras/models/jobs.py +30 -0
  37. nautobot/extras/models/models.py +2 -4
  38. nautobot/extras/signals.py +6 -1
  39. nautobot/extras/tables.py +3 -0
  40. nautobot/extras/templates/extras/jobbutton_retrieve.html +6 -2
  41. nautobot/extras/templatetags/job_buttons.py +2 -2
  42. nautobot/extras/tests/git_data/01-valid-files/__init__.py +0 -0
  43. nautobot/extras/tests/git_data/01-valid-files/config_context_schemas/schema-1.yaml +18 -0
  44. nautobot/extras/tests/git_data/01-valid-files/config_contexts/context.yaml +12 -0
  45. nautobot/extras/tests/git_data/01-valid-files/config_contexts/devices/test-device.json +3 -0
  46. nautobot/extras/tests/git_data/01-valid-files/config_contexts/locations/Test Location.json +7 -0
  47. nautobot/extras/tests/git_data/01-valid-files/export_templates/dcim/device/template.j2 +3 -0
  48. nautobot/extras/tests/git_data/01-valid-files/export_templates/dcim/device/template2.html +4 -0
  49. nautobot/extras/tests/git_data/01-valid-files/export_templates/ipam/vlan/template.j2 +3 -0
  50. nautobot/extras/tests/git_data/01-valid-files/jobs/__init__.py +5 -0
  51. nautobot/extras/tests/git_data/01-valid-files/jobs/my_job.py +16 -0
  52. nautobot/extras/tests/git_data/02-invalid-files/__init__.py +0 -0
  53. nautobot/extras/tests/git_data/02-invalid-files/config_context_schemas/badschema1.json +2 -0
  54. nautobot/extras/tests/git_data/02-invalid-files/config_context_schemas/badschema2.json +1 -0
  55. nautobot/extras/tests/git_data/02-invalid-files/config_contexts/badcontext1.json +2 -0
  56. nautobot/extras/tests/git_data/02-invalid-files/config_contexts/badcontext2.json +1 -0
  57. nautobot/extras/tests/git_data/02-invalid-files/config_contexts/badcontext3.json +3 -0
  58. nautobot/extras/tests/git_data/02-invalid-files/config_contexts/devices/nosuchdevice.json +1 -0
  59. nautobot/extras/tests/git_data/02-invalid-files/dcim/template.j2 +0 -0
  60. nautobot/extras/tests/git_data/02-invalid-files/devices/template.j2 +0 -0
  61. nautobot/extras/tests/git_data/02-invalid-files/export_templates/dcim/nosuchmodel/template.j2 +3 -0
  62. nautobot/extras/tests/git_data/02-invalid-files/export_templates/nosuchapp/device/template.j2 +3 -0
  63. nautobot/extras/tests/git_data/02-invalid-files/jobs/__init__.py +2 -0
  64. nautobot/extras/tests/git_data/02-invalid-files/jobs/importerror.py +1 -0
  65. nautobot/extras/tests/git_data/02-invalid-files/jobs/syntaxerror.py +1 -0
  66. nautobot/extras/tests/git_helper.py +76 -0
  67. nautobot/extras/tests/test_api.py +52 -13
  68. nautobot/extras/tests/test_context_managers.py +33 -1
  69. nautobot/extras/tests/test_datasources.py +94 -276
  70. nautobot/extras/tests/test_filters.py +69 -0
  71. nautobot/extras/tests/test_forms.py +0 -3
  72. nautobot/extras/tests/test_models.py +8 -3
  73. nautobot/extras/tests/test_views.py +69 -11
  74. nautobot/extras/views.py +12 -10
  75. nautobot/ipam/filters.py +9 -1
  76. nautobot/ipam/forms.py +26 -0
  77. nautobot/ipam/tables.py +1 -1
  78. nautobot/ipam/tests/test_filters.py +15 -0
  79. nautobot/ipam/tests/test_views.py +9 -2
  80. nautobot/ipam/views.py +11 -0
  81. nautobot/project-static/docs/404.html +84 -9
  82. nautobot/project-static/docs/apps/index.html +97 -11
  83. nautobot/project-static/docs/apps/nautobot-apps.html +97 -11
  84. nautobot/project-static/docs/assets/app-icons/icon-CapacityMetrics.svg +1 -0
  85. nautobot/project-static/docs/assets/app-icons/icon-CircuitMaintenance.png +0 -0
  86. nautobot/project-static/docs/assets/javascripts/{bundle.3220b9d7.min.js → bundle.ad660dcc.min.js} +6 -6
  87. nautobot/project-static/docs/assets/javascripts/{bundle.3220b9d7.min.js.map → bundle.ad660dcc.min.js.map} +3 -3
  88. nautobot/project-static/docs/assets/javascripts/glightbox.min.js +1 -0
  89. nautobot/project-static/docs/assets/stylesheets/glightbox.min.css +1 -0
  90. nautobot/project-static/docs/assets/stylesheets/{main.66ac8b77.min.css → main.6543a935.min.css} +1 -1
  91. nautobot/project-static/docs/assets/stylesheets/{main.66ac8b77.min.css.map → main.6543a935.min.css.map} +1 -1
  92. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +97 -11
  93. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +97 -11
  94. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +97 -11
  95. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +97 -11
  96. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +97 -11
  97. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +97 -11
  98. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +97 -11
  99. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +97 -11
  100. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +97 -11
  101. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +97 -11
  102. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +97 -11
  103. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +97 -11
  104. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +97 -11
  105. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +97 -11
  106. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +157 -13
  107. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +97 -11
  108. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +97 -11
  109. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +97 -11
  110. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +107 -12
  111. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +97 -11
  112. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +97 -11
  113. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +144 -12
  114. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +97 -11
  115. nautobot/project-static/docs/development/apps/api/configuration-view.html +97 -11
  116. nautobot/project-static/docs/development/apps/api/database-backend-config.html +97 -11
  117. nautobot/project-static/docs/development/apps/api/models/django-admin.html +97 -11
  118. nautobot/project-static/docs/development/apps/api/models/global-search.html +97 -11
  119. nautobot/project-static/docs/development/apps/api/models/graphql.html +97 -11
  120. nautobot/project-static/docs/development/apps/api/models/index.html +97 -11
  121. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +97 -11
  122. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +97 -11
  123. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +98 -12
  124. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +97 -11
  125. nautobot/project-static/docs/development/apps/api/platform-features/index.html +97 -11
  126. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +97 -11
  127. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +97 -11
  128. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +97 -11
  129. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +97 -11
  130. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +97 -11
  131. nautobot/project-static/docs/development/apps/api/prometheus.html +97 -11
  132. nautobot/project-static/docs/development/apps/api/setup.html +97 -11
  133. nautobot/project-static/docs/development/apps/api/testing.html +97 -11
  134. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +97 -11
  135. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +97 -11
  136. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +97 -11
  137. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +97 -11
  138. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +97 -11
  139. nautobot/project-static/docs/development/apps/api/views/base-template.html +97 -11
  140. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +97 -11
  141. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +97 -11
  142. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +97 -11
  143. nautobot/project-static/docs/development/apps/api/views/index.html +97 -11
  144. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +97 -11
  145. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +97 -11
  146. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +97 -11
  147. nautobot/project-static/docs/development/apps/api/views/notes.html +97 -11
  148. nautobot/project-static/docs/development/apps/api/views/rest-api.html +97 -11
  149. nautobot/project-static/docs/development/apps/api/views/urls.html +97 -11
  150. nautobot/project-static/docs/development/apps/index.html +97 -11
  151. nautobot/project-static/docs/development/apps/migration/code-updates.html +98 -12
  152. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +97 -11
  153. nautobot/project-static/docs/development/apps/migration/from-v1.html +99 -13
  154. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +97 -11
  155. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +97 -11
  156. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +97 -11
  157. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +97 -11
  158. nautobot/project-static/docs/development/apps/porting-from-netbox.html +97 -11
  159. nautobot/project-static/docs/development/core/application-registry.html +98 -12
  160. nautobot/project-static/docs/development/core/best-practices.html +97 -11
  161. nautobot/project-static/docs/development/core/bootstrap-ui.html +97 -11
  162. nautobot/project-static/docs/development/core/caching.html +97 -11
  163. nautobot/project-static/docs/development/core/controllers.html +97 -11
  164. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +97 -11
  165. nautobot/project-static/docs/development/core/generic-views.html +97 -11
  166. nautobot/project-static/docs/development/core/getting-started.html +99 -13
  167. nautobot/project-static/docs/development/core/homepage.html +97 -11
  168. nautobot/project-static/docs/development/core/index.html +97 -11
  169. nautobot/project-static/docs/development/core/model-checklist.html +97 -11
  170. nautobot/project-static/docs/development/core/model-features.html +97 -11
  171. nautobot/project-static/docs/development/core/natural-keys.html +97 -11
  172. nautobot/project-static/docs/development/core/navigation-menu.html +97 -11
  173. nautobot/project-static/docs/development/core/release-checklist.html +97 -11
  174. nautobot/project-static/docs/development/core/role-internals.html +97 -11
  175. nautobot/project-static/docs/development/core/settings.html +97 -11
  176. nautobot/project-static/docs/development/core/style-guide.html +97 -11
  177. nautobot/project-static/docs/development/core/templates.html +97 -11
  178. nautobot/project-static/docs/development/core/testing.html +98 -12
  179. nautobot/project-static/docs/development/core/user-preferences.html +97 -11
  180. nautobot/project-static/docs/development/index.html +97 -11
  181. nautobot/project-static/docs/development/jobs/index.html +97 -11
  182. nautobot/project-static/docs/development/jobs/migration/from-v1.html +97 -11
  183. nautobot/project-static/docs/index.html +13 -8362
  184. nautobot/project-static/docs/objects.inv +0 -0
  185. nautobot/project-static/docs/overview/application_stack.html +8229 -0
  186. nautobot/project-static/docs/overview/design_philosophy.html +8158 -0
  187. nautobot/project-static/docs/overview/index.html +8230 -0
  188. nautobot/project-static/docs/release-notes/index.html +97 -11
  189. nautobot/project-static/docs/release-notes/version-1.0.html +98 -12
  190. nautobot/project-static/docs/release-notes/version-1.1.html +97 -11
  191. nautobot/project-static/docs/release-notes/version-1.2.html +99 -13
  192. nautobot/project-static/docs/release-notes/version-1.3.html +98 -12
  193. nautobot/project-static/docs/release-notes/version-1.4.html +99 -13
  194. nautobot/project-static/docs/release-notes/version-1.5.html +99 -13
  195. nautobot/project-static/docs/release-notes/version-1.6.html +626 -160
  196. nautobot/project-static/docs/release-notes/version-2.0.html +100 -14
  197. nautobot/project-static/docs/release-notes/version-2.1.html +97 -11
  198. nautobot/project-static/docs/release-notes/version-2.2.html +546 -107
  199. nautobot/project-static/docs/requirements.txt +3 -2
  200. nautobot/project-static/docs/search/search_index.json +1 -1
  201. nautobot/project-static/docs/sitemap.xml +268 -258
  202. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  203. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +97 -11
  204. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +97 -11
  205. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +99 -13
  206. nautobot/project-static/docs/user-guide/administration/configuration/index.html +98 -12
  207. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +100 -14
  208. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +97 -11
  209. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +97 -11
  210. nautobot/project-static/docs/user-guide/administration/guides/caching.html +97 -11
  211. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +97 -11
  212. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +97 -11
  213. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +97 -11
  214. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +97 -11
  215. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +98 -12
  216. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +97 -11
  217. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +97 -11
  218. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +97 -11
  219. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +97 -11
  220. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +97 -11
  221. nautobot/project-static/docs/user-guide/administration/installation/index.html +97 -11
  222. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +97 -11
  223. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +101 -15
  224. nautobot/project-static/docs/user-guide/administration/installation/services.html +98 -12
  225. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +97 -11
  226. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +97 -11
  227. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +97 -11
  228. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +97 -11
  229. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +97 -11
  230. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +97 -11
  231. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +97 -11
  232. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +97 -11
  233. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +97 -11
  234. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +97 -11
  235. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +98 -12
  236. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +97 -11
  237. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +97 -11
  238. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +97 -30
  239. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/tables/v2-code-location-changes.yaml +1 -1
  240. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +100 -14
  241. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +97 -11
  242. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +97 -11
  243. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +97 -11
  244. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +97 -11
  245. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +97 -11
  246. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +97 -11
  247. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +97 -11
  248. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +97 -11
  249. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +97 -11
  250. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +97 -11
  251. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +97 -11
  252. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +97 -11
  253. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +97 -11
  254. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +99 -13
  255. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +97 -11
  256. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +97 -11
  257. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +97 -11
  258. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +97 -11
  259. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +98 -12
  260. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +97 -11
  261. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +97 -11
  262. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +97 -11
  263. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +97 -11
  264. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +97 -11
  265. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +97 -11
  266. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +97 -11
  267. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +97 -11
  268. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +97 -11
  269. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +97 -11
  270. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +97 -11
  271. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +97 -11
  272. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +97 -11
  273. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +97 -11
  274. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +97 -11
  275. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +97 -11
  276. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +97 -11
  277. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +97 -11
  278. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +97 -11
  279. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +97 -11
  280. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +97 -11
  281. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +97 -11
  282. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +97 -11
  283. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +97 -11
  284. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +97 -11
  285. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +97 -11
  286. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +97 -11
  287. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +97 -11
  288. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +97 -11
  289. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +97 -11
  290. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +97 -11
  291. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +97 -11
  292. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +97 -11
  293. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +97 -11
  294. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +97 -11
  295. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +97 -11
  296. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +97 -11
  297. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +97 -11
  298. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +97 -11
  299. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +97 -11
  300. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +97 -11
  301. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +97 -11
  302. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +97 -11
  303. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +97 -11
  304. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +97 -11
  305. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +97 -11
  306. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +97 -11
  307. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +100 -14
  308. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +97 -11
  309. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +97 -11
  310. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +98 -12
  311. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +97 -11
  312. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +97 -11
  313. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +97 -11
  314. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +97 -11
  315. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +98 -12
  316. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +97 -11
  317. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +97 -11
  318. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +97 -11
  319. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +97 -11
  320. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +97 -11
  321. nautobot/project-static/docs/user-guide/index.html +100 -14
  322. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +97 -11
  323. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +97 -11
  324. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +97 -11
  325. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +97 -11
  326. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +97 -11
  327. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +97 -11
  328. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +97 -11
  329. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +97 -11
  330. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +97 -11
  331. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +97 -11
  332. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +97 -11
  333. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +98 -12
  334. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +98 -12
  335. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +102 -15
  336. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +97 -11
  337. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +99 -13
  338. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +99 -13
  339. nautobot/project-static/docs/user-guide/platform-functionality/note.html +97 -11
  340. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +97 -11
  341. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +97 -11
  342. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +97 -11
  343. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +97 -11
  344. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +97 -11
  345. nautobot/project-static/docs/user-guide/platform-functionality/role.html +98 -44
  346. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +97 -11
  347. nautobot/project-static/docs/user-guide/platform-functionality/status.html +97 -11
  348. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +97 -11
  349. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +97 -11
  350. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +97 -11
  351. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +97 -11
  352. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +97 -11
  353. nautobot/project-static/js/forms.js +2 -1
  354. nautobot/tenancy/forms.py +9 -0
  355. nautobot/tenancy/views.py +1 -0
  356. nautobot/virtualization/forms.py +18 -6
  357. nautobot/virtualization/templates/virtualization/clustertype.html +2 -2
  358. nautobot/virtualization/views.py +2 -0
  359. {nautobot-2.2.4.dist-info → nautobot-2.2.6.dist-info}/METADATA +1 -1
  360. {nautobot-2.2.4.dist-info → nautobot-2.2.6.dist-info}/RECORD +364 -331
  361. nautobot/extras/tests/test_git.py +0 -23
  362. {nautobot-2.2.4.dist-info → nautobot-2.2.6.dist-info}/LICENSE.txt +0 -0
  363. {nautobot-2.2.4.dist-info → nautobot-2.2.6.dist-info}/NOTICE +0 -0
  364. {nautobot-2.2.4.dist-info → nautobot-2.2.6.dist-info}/WHEEL +0 -0
  365. {nautobot-2.2.4.dist-info → nautobot-2.2.6.dist-info}/entry_points.txt +0 -0
@@ -1,4 +1,3 @@
1
- import json
2
1
  import os
3
2
  import sys
4
3
  import tempfile
@@ -6,6 +5,7 @@ from unittest import mock
6
5
  import uuid
7
6
 
8
7
  from celery.exceptions import NotRegistered
8
+ from django.conf import settings
9
9
  from django.contrib.contenttypes.models import ContentType
10
10
  from django.core.exceptions import ValidationError
11
11
  from django.test import RequestFactory
@@ -31,6 +31,8 @@ from nautobot.extras.models import (
31
31
  ExportTemplate,
32
32
  GitRepository,
33
33
  Job,
34
+ JobButton,
35
+ JobHook,
34
36
  JobLogEntry,
35
37
  JobResult,
36
38
  Role,
@@ -39,10 +41,10 @@ from nautobot.extras.models import (
39
41
  SecretsGroupAssociation,
40
42
  Status,
41
43
  )
44
+ from nautobot.extras.tests.git_helper import create_and_populate_git_repository
42
45
  from nautobot.ipam.models import VLAN
43
46
 
44
47
 
45
- @mock.patch("nautobot.extras.datasources.git.GitRepo")
46
48
  class GitTest(TransactionTestCase):
47
49
  """
48
50
  Tests for Git repository handling.
@@ -51,7 +53,6 @@ class GitTest(TransactionTestCase):
51
53
  """
52
54
 
53
55
  databases = ("default", "job_logs")
54
- COMMIT_HEXSHA = "88dd9cd78df89e887ee90a1d209a3e9a04e8c841"
55
56
 
56
57
  def setUp(self):
57
58
  super().setUp()
@@ -80,11 +81,15 @@ class GitTest(TransactionTestCase):
80
81
  status=status,
81
82
  )
82
83
 
84
+ self.tempdir = tempfile.TemporaryDirectory() # pylint: disable=consider-using-with
85
+ create_and_populate_git_repository(self.tempdir.name)
86
+
83
87
  self.repo_slug = "test_git_repo"
84
88
  self.repo = GitRepository(
85
89
  name="Test Git Repository",
86
90
  slug=self.repo_slug,
87
- remote_url="http://localhost/git.git",
91
+ remote_url="file://" + self.tempdir.name, # file:// URLs aren't permitted normally, but very useful here!
92
+ branch="empty-repo",
88
93
  # Provide everything we know we can provide
89
94
  provided_contents=[entry.content_identifier for entry in get_datasource_contents("extras.gitrepository")],
90
95
  )
@@ -92,138 +97,34 @@ class GitTest(TransactionTestCase):
92
97
 
93
98
  self.job_result = JobResult.objects.create(name=self.repo.name)
94
99
 
95
- self.config_context_schema = {
96
- "_metadata": {
97
- "name": "Config Context Schema 1",
98
- "description": "Schema for defining first names, last names and ages.",
99
- },
100
- "data_schema": {
101
- "title": "Person",
102
- "type": "object",
103
- "properties": {
104
- "firstName": {
105
- "type": "string",
106
- "description": "The person's first name.",
107
- },
108
- "lastName": {
109
- "type": "string",
110
- "description": "The person's last name.",
111
- },
112
- "age": {
113
- "description": "Age in years which must be equal to or greater than zero.",
114
- "type": "integer",
115
- "minimum": 0,
116
- },
117
- },
118
- },
119
- }
120
-
121
100
  def tearDown(self):
122
101
  if f"{self.repo_slug}.jobs" in sys.modules:
123
102
  del sys.modules[f"{self.repo_slug}.jobs"]
124
103
  if f"{self.repo_slug}" in sys.modules:
125
104
  del sys.modules[f"{self.repo_slug}"]
105
+ self.tempdir.cleanup()
126
106
  if self.repo is not None:
127
107
  self.repo.delete()
128
108
  super().tearDown()
129
109
 
130
- def populate_repo(self, path, url, *args, **kwargs):
131
- os.makedirs(path, exist_ok=True)
132
-
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)
140
-
141
- with open(os.path.join(path, "__init__.py"), "w") as fd:
142
- # Required for job importing
143
- pass
144
-
145
- with open(os.path.join(path, "config_contexts", "context.yaml"), "w") as fd:
146
- yaml.dump(
147
- {
148
- "_metadata": {
149
- "name": "Frobozz 1000 NTP servers",
150
- "weight": 1500,
151
- "description": "NTP servers for Frobozz 1000 devices **only**",
152
- "is_active": True,
153
- "config_context_schema": "Config Context Schema 1",
154
- "device_types": [{"model": self.device_type.model}],
155
- },
156
- "ntp-servers": ["172.16.10.22", "172.16.10.33"],
157
- },
158
- fd,
159
- )
160
-
161
- with open(os.path.join(path, "config_contexts", "locations", f"{self.location.name}.json"), "w") as fd:
162
- json.dump(
163
- {
164
- "_metadata": {"name": "Location context", "is_active": False},
165
- "domain_name": "example.com",
166
- },
167
- fd,
168
- )
169
-
170
- with open(os.path.join(path, "config_contexts", "devices", f"{self.device.name}.json"), "w") as fd:
171
- json.dump({"dns-servers": ["8.8.8.8"]}, fd)
172
-
173
- with open(os.path.join(path, "config_context_schemas", "schema-1.yaml"), "w") as fd:
174
- yaml.dump(self.config_context_schema, fd)
175
-
176
- with open(os.path.join(path, "export_templates", "dcim", "device", "template.j2"), "w") as fd:
177
- fd.write("{% for device in queryset %}\n{{ device.name }}\n{% endfor %}")
178
-
179
- with open(os.path.join(path, "export_templates", "dcim", "device", "template2.html"), "w") as fd:
180
- fd.write("<!DOCTYPE html>/n{% for device in queryset %}\n{{ device.name }}\n{% endfor %}")
181
-
182
- with open(os.path.join(path, "export_templates", "ipam", "vlan", "template.j2"), "w") as fd:
183
- fd.write("{% for vlan in queryset %}\n{{ vlan.name }}\n{% endfor %}")
184
-
185
- with open(os.path.join(path, "jobs", "__init__.py"), "w") as fd:
186
- fd.write("from nautobot.core.celery import register_jobs\nfrom .my_job import MyJob\nregister_jobs(MyJob)")
187
-
188
- with open(os.path.join(path, "jobs", "my_job.py"), "w") as fd:
189
- fd.write("from nautobot.extras.jobs import Job\nclass MyJob(Job):\n def run(self):\n pass")
190
-
191
- return mock.DEFAULT
192
-
193
- def empty_repo(self, path, url, *args, **kwargs):
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
207
- return mock.DEFAULT
208
-
209
110
  def assert_repo_slug_valid_python_package_name(self):
210
111
  git_repository = GitRepository.objects.create(
211
112
  name="1 Very-Bad Git_____Repo Name (2)", remote_url="http://localhost/git.git"
212
113
  )
213
114
  self.assertEqual(git_repository.slug, "a1_very_bad_git_____repo_name_2")
214
115
 
215
- def assert_config_context_schema_record_exists(self, name):
216
- """Helper Func to assert ConfigContextSchema with name=name exists"""
116
+ def assert_config_context_schema_record_exists(self, name, filename="schema-1.yaml"):
117
+ """Assert that a ConfigContextSchema record exists with the expected name and data_schema."""
217
118
  config_context_schema_record = ConfigContextSchema.objects.get(
218
119
  name=name,
219
120
  owner_object_id=self.repo.pk,
220
121
  owner_content_type=ContentType.objects.get_for_model(GitRepository),
221
122
  )
222
- config_context_schema = self.config_context_schema
223
- config_context_schema_metadata = config_context_schema["_metadata"]
224
123
  self.assertIsNotNone(config_context_schema_record)
225
- self.assertEqual(config_context_schema_metadata["name"], config_context_schema_record.name)
226
- self.assertEqual(config_context_schema["data_schema"], config_context_schema_record.data_schema)
124
+ with open(os.path.join(settings.GIT_ROOT, self.repo.slug, "config_context_schemas", filename)) as fd:
125
+ config_context_schema_data = yaml.safe_load(fd)
126
+ self.assertEqual(config_context_schema_data["_metadata"]["name"], config_context_schema_record.name)
127
+ self.assertEqual(config_context_schema_data["data_schema"], config_context_schema_record.data_schema)
227
128
 
228
129
  def assert_device_exists(self, name):
229
130
  """Helper function to assert device exists"""
@@ -258,7 +159,7 @@ class GitTest(TransactionTestCase):
258
159
  {"ntp-servers": ["172.16.10.22", "172.16.10.33"]},
259
160
  config_context.data,
260
161
  )
261
- self.assertEqual(self.config_context_schema["_metadata"]["name"], config_context.config_context_schema.name)
162
+ self.assertIsNotNone(config_context.config_context_schema)
262
163
 
263
164
  def assert_implicit_config_context_exists(self, name):
264
165
  """Helper function to assert that an 'implicit' ConfigContext exists and is configured appropriately."""
@@ -296,10 +197,10 @@ class GitTest(TransactionTestCase):
296
197
  )
297
198
  self.assertIsNotNone(export_template_vlan)
298
199
 
299
- def assert_job_exists(self, installed=True):
200
+ def assert_job_exists(self, name="MyJob", installed=True):
300
201
  """Helper function to assert JobModel and registered Job exist."""
301
202
  # Is it registered correctly in the database?
302
- job_model = Job.objects.get(name="MyJob", module_name=f"{self.repo_slug}.jobs.my_job", job_class_name="MyJob")
203
+ job_model = Job.objects.get(name=name, module_name=f"{self.repo_slug}.jobs.my_job", job_class_name=name)
303
204
  self.assertIsNotNone(job_model)
304
205
  if installed:
305
206
  self.assertTrue(job_model.installed)
@@ -312,20 +213,12 @@ class GitTest(TransactionTestCase):
312
213
  with self.assertRaises(NotRegistered):
313
214
  job_model.job_task
314
215
 
315
- def test_pull_git_repository_and_refresh_data_with_no_data(self, MockGitRepo):
216
+ def test_pull_git_repository_and_refresh_data_with_no_data(self):
316
217
  """
317
218
  The pull_git_repository_and_refresh_data job should fail if the given repo is empty.
318
219
  """
319
220
  with tempfile.TemporaryDirectory() as tempdir:
320
221
  with self.settings(GIT_ROOT=tempdir):
321
-
322
- def create_empty_repo(path, url):
323
- os.makedirs(path, exist_ok=True)
324
- return mock.DEFAULT
325
-
326
- MockGitRepo.side_effect = create_empty_repo
327
- MockGitRepo.return_value.checkout.return_value = (self.COMMIT_HEXSHA, True)
328
-
329
222
  # Run the Git operation and refresh the object from the DB
330
223
  # pull_git_repository_and_refresh_data(self.repo.pk, self.mock_request, self.job_result.pk)
331
224
  job_result = create_job_result_and_run_job(
@@ -341,8 +234,6 @@ class GitTest(TransactionTestCase):
341
234
  (job_result.result, list(job_result.job_log_entries.values_list("message", "log_object"))),
342
235
  )
343
236
  self.repo.refresh_from_db()
344
- self.assertEqual(self.repo.current_head, self.COMMIT_HEXSHA, job_result.result)
345
- MockGitRepo.assert_called_with(os.path.join(tempdir, self.repo.slug), "http://localhost/git.git")
346
237
 
347
238
  log_entries = JobLogEntry.objects.filter(job_result=job_result)
348
239
  failure_logs = log_entries.filter(log_level=LogLevelChoices.LOG_ERROR)
@@ -354,20 +245,14 @@ class GitTest(TransactionTestCase):
354
245
  print(job_result.traceback)
355
246
  raise
356
247
 
248
+ @mock.patch("nautobot.extras.datasources.git.GitRepo")
357
249
  def test_pull_git_repository_and_refresh_data_with_secrets(self, MockGitRepo):
358
250
  """
359
251
  The pull_git_repository_and_refresh_data job should correctly make use of secrets.
360
252
  """
361
253
  with tempfile.TemporaryDirectory() as tempdir:
362
254
  with self.settings(GIT_ROOT=tempdir):
363
-
364
- def create_empty_repo(path, url):
365
- os.makedirs(path, exist_ok=True)
366
- return mock.DEFAULT
367
-
368
- MockGitRepo.side_effect = create_empty_repo
369
- MockGitRepo.return_value.checkout.return_value = (self.COMMIT_HEXSHA, True)
370
-
255
+ MockGitRepo.return_value.checkout.return_value = ("0123456789abcdef", True)
371
256
  with open(os.path.join(tempdir, "username.txt"), "wt") as handle:
372
257
  handle.write("núñez")
373
258
 
@@ -399,6 +284,7 @@ class GitTest(TransactionTestCase):
399
284
  )
400
285
 
401
286
  self.repo.secrets_group = secrets_group
287
+ self.repo.remote_url = "http://localhost/git.git"
402
288
  self.repo.provided_contents.remove("extras.job") # avoid failing due to lack of jobs module
403
289
  self.repo.save()
404
290
 
@@ -422,16 +308,15 @@ class GitTest(TransactionTestCase):
422
308
  "http://n%C3%BA%C3%B1ez:1%3A3%40%2F%3F%3Dab%40@localhost/git.git",
423
309
  )
424
310
 
425
- def test_pull_git_repository_and_refresh_data_with_valid_data(self, MockGitRepo):
311
+ def test_pull_git_repository_and_refresh_data_with_valid_data(self):
426
312
  """
427
313
  The test_pull_git_repository_and_refresh_data job should succeed if valid data is present in the repo.
428
314
  """
429
315
  with tempfile.TemporaryDirectory() as tempdir:
430
316
  with self.settings(GIT_ROOT=tempdir):
431
- MockGitRepo.side_effect = self.populate_repo
432
- MockGitRepo.return_value.checkout.return_value = (self.COMMIT_HEXSHA, True)
433
-
434
317
  # Run the Git operation and refresh the object from the DB
318
+ self.repo.branch = "valid-files" # actually a tag
319
+ self.repo.save()
435
320
  job_model = GitRepositorySync().job_model
436
321
  job_result = run_job_for_testing(job=job_model, repository=self.repo.pk)
437
322
  job_result.refresh_from_db()
@@ -462,14 +347,23 @@ class GitTest(TransactionTestCase):
462
347
  # Case when ContentType.model != ContentType.name, template was added and deleted during sync (#570)
463
348
  self.assert_export_template_vlan_exists("template.j2")
464
349
 
465
- # Make sure Job was successfully loaded from file and registered as a JobModel
466
- self.assert_job_exists()
350
+ # Make sure Jobs were successfully loaded from file and registered as JobModels
351
+ self.assert_job_exists(name="MyJob")
352
+ self.assert_job_exists(name="MyJobButtonReceiver")
353
+ self.assert_job_exists(name="MyJobHookReceiver")
467
354
 
468
- # Now "resync" the repository, but now those files no longer exist in the repository
469
- MockGitRepo.side_effect = self.empty_repo
355
+ # Create JobButton and JobHook
356
+ JobButton.objects.create(
357
+ name="MyJobButton", enabled=True, text="Click me", job=Job.objects.get(name="MyJobButtonReceiver")
358
+ )
359
+ JobHook.objects.create(name="MyJobHook", enabled=True, job=Job.objects.get(name="MyJobHookReceiver"))
470
360
 
471
- # For verisimilitude, don't re-use the old request and job_result
472
- self.mock_request.id = uuid.uuid4()
361
+ # TODO: test successful sync against a branch name or a commit hash as well
362
+
363
+ # Now "resync" the repository, but now those files no longer exist in the repository
364
+ self.repo.refresh_from_db()
365
+ self.repo.branch = "empty-repo" # actually a tag
366
+ self.repo.save()
473
367
 
474
368
  # Run the Git operation and refresh the object from the DB
475
369
  job_result = run_job_for_testing(job=job_model, repository=self.repo.pk)
@@ -489,6 +383,7 @@ class GitTest(TransactionTestCase):
489
383
  owner_object_id=self.repo.pk,
490
384
  )
491
385
  ),
386
+ list(job_result.job_log_entries.values_list("message", flat=True)),
492
387
  )
493
388
  self.assertEqual(
494
389
  [],
@@ -498,6 +393,7 @@ class GitTest(TransactionTestCase):
498
393
  owner_object_id=self.repo.pk,
499
394
  )
500
395
  ),
396
+ list(job_result.job_log_entries.values_list("message", flat=True)),
501
397
  )
502
398
  device = Device.objects.get(name=self.device.name)
503
399
  self.assertIsNone(device.local_config_context_data)
@@ -505,61 +401,23 @@ class GitTest(TransactionTestCase):
505
401
 
506
402
  # Verify that Job database record still exists but code is no longer installed/loaded
507
403
  self.assert_job_exists(installed=False)
404
+ self.assert_job_exists(name="MyJobButtonReceiver", installed=False)
405
+ self.assert_job_exists(name="MyJobHookReceiver", installed=False)
406
+
407
+ # Verify that JobButton and JobHook are auto-disabled since the jobs are no longer available
408
+ jb = JobButton.objects.get(name="MyJobButton")
409
+ self.assertFalse(jb.enabled)
410
+ jh = JobHook.objects.get(name="MyJobHook")
411
+ self.assertFalse(jh.enabled)
508
412
 
509
- def test_pull_git_repository_and_refresh_data_with_bad_data(self, MockGitRepo):
413
+ def test_pull_git_repository_and_refresh_data_with_bad_data(self):
510
414
  """
511
415
  The test_pull_git_repository_and_refresh_data job should gracefully handle bad data in the Git repository
512
416
  """
513
417
  with tempfile.TemporaryDirectory() as tempdir:
514
418
  with self.settings(GIT_ROOT=tempdir):
515
-
516
- def populate_repo(path, url):
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)
524
- # Incorrect directories
525
- os.makedirs(os.path.join(path, "devices"), exist_ok=True)
526
- os.makedirs(os.path.join(path, "dcim"), exist_ok=True)
527
- with open(os.path.join(path, "__init__.py"), "w") as fd:
528
- pass
529
- # Malformed JSON
530
- with open(os.path.join(path, "config_contexts", "context.json"), "w") as fd:
531
- fd.write('{"data": ')
532
- # Valid JSON but missing required keys
533
- with open(os.path.join(path, "config_contexts", "context2.json"), "w") as fd:
534
- fd.write("{}")
535
- with open(os.path.join(path, "config_contexts", "context3.json"), "w") as fd:
536
- fd.write('{"_metadata": {}}')
537
- # Malformed JSON
538
- with open(os.path.join(path, "config_context_schemas", "schema-1.yaml"), "w") as fd:
539
- fd.write('{"data": ')
540
- # Valid JSON but missing required keys
541
- with open(os.path.join(path, "config_context_schemas", "schema-2.yaml"), "w") as fd:
542
- fd.write("{}")
543
- # No such device
544
- with open(os.path.join(path, "config_contexts", "devices", "nosuchdevice.json"), "w") as fd:
545
- fd.write("{}")
546
- # Invalid paths
547
- with open(os.path.join(path, "export_templates", "nosuchapp", "device", "template.j2"), "w") as fd:
548
- fd.write("{% for device in queryset %}\n{{ device.name }}\n{% endfor %}")
549
- with open(os.path.join(path, "export_templates", "dcim", "nosuchmodel", "template.j2"), "w") as fd:
550
- fd.write("{% for device in queryset %}\n{{ device.name }}\n{% endfor %}")
551
- # Malformed Python
552
- with open(os.path.join(path, "jobs", "syntaxerror.py"), "w") as fd:
553
- fd.write("print(")
554
- with open(os.path.join(path, "jobs", "importerror.py"), "w") as fd:
555
- fd.write("import nosuchmodule")
556
- with open(os.path.join(path, "jobs", "__init__.py"), "w") as fd:
557
- fd.write("import .syntaxerror\nimport .importerror")
558
- return mock.DEFAULT
559
-
560
- MockGitRepo.side_effect = populate_repo
561
- MockGitRepo.return_value.checkout.return_value = (self.COMMIT_HEXSHA, True)
562
-
419
+ self.repo.branch = "invalid-files"
420
+ self.repo.save()
563
421
  # Run the Git operation and refresh the object from the DB
564
422
  job_model = GitRepositorySync().job_model
565
423
  self.assertIsNotNone(job_model)
@@ -604,25 +462,25 @@ class GitTest(TransactionTestCase):
604
462
  try:
605
463
  failure_logs.get(
606
464
  grouping="config context schemas",
607
- message__contains="Error in loading config context schema data from `schema-1.yaml`",
465
+ message__contains="Error in loading config context schema data from `badschema1.json`",
608
466
  )
609
467
  failure_logs.get(
610
468
  grouping="config context schemas",
611
- message__contains="Error in loading config context schema data from `schema-2.yaml`: "
469
+ message__contains="Error in loading config context schema data from `badschema2.json`: "
612
470
  "data is missing the required `_metadata` key",
613
471
  )
614
472
  failure_logs.get(
615
473
  grouping="config contexts",
616
- message__contains="Error in loading config context data from `context.json`",
474
+ message__contains="Error in loading config context data from `badcontext1.json`",
617
475
  )
618
476
  failure_logs.get(
619
477
  grouping="config contexts",
620
- message__contains="Error in loading config context data from `context2.json`: "
478
+ message__contains="Error in loading config context data from `badcontext2.json`: "
621
479
  "data is missing the required `_metadata` key",
622
480
  )
623
481
  failure_logs.get(
624
482
  grouping="config contexts",
625
- message__contains="Error in loading config context data from `context3.json`: "
483
+ message__contains="Error in loading config context data from `badcontext3.json`: "
626
484
  "data `_metadata` is missing the required `name` key",
627
485
  )
628
486
  failure_logs.get(
@@ -640,67 +498,14 @@ class GitTest(TransactionTestCase):
640
498
  print(job_result.traceback)
641
499
  raise
642
500
 
643
- def test_delete_git_repository_cleanup(self, MockGitRepo):
501
+ def test_delete_git_repository_cleanup(self):
644
502
  """
645
503
  When deleting a GitRepository record, the data that it owned should also be deleted.
646
504
  """
647
505
  with tempfile.TemporaryDirectory() as tempdir:
648
506
  with self.settings(GIT_ROOT=tempdir):
649
-
650
- def populate_repo(path, url):
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)
657
- with open(os.path.join(path, "__init__.py"), "w") as fd:
658
- pass
659
- with open(os.path.join(path, "config_contexts", "context.yaml"), "w") as fd:
660
- yaml.dump(
661
- {
662
- "_metadata": {
663
- "name": "Region NYC servers",
664
- "weight": 1500,
665
- "description": "NTP servers for region NYC",
666
- "is_active": True,
667
- # Changing this from `config_context_schema` to `schema` to assert that schema can
668
- # be used inplace of `config_context_schema`.
669
- # TODO(timizuo): Replace `schema` with `config_context_schema` when `schema`
670
- # backwards-compatibility is removed.
671
- "schema": "Config Context Schema 1",
672
- },
673
- "ntp-servers": ["172.16.10.22", "172.16.10.33"],
674
- },
675
- fd,
676
- )
677
- with open(
678
- os.path.join(path, "config_contexts", "devices", "test-device.json"),
679
- "w",
680
- ) as fd:
681
- json.dump({"dns-servers": ["8.8.8.8"]}, fd)
682
- with open(os.path.join(path, "config_context_schemas", "schema-1.yaml"), "w") as fd:
683
- yaml.dump(self.config_context_schema, fd)
684
- with open(
685
- os.path.join(path, "export_templates", "dcim", "device", "template.j2"),
686
- "w",
687
- ) as fd:
688
- fd.write("{% for device in queryset %}\n{{ device.name }}\n{% endfor %}")
689
- with open(os.path.join(path, "jobs", "__init__.py"), "w") as fd:
690
- fd.write(
691
- "from nautobot.core.celery import register_jobs\nfrom .my_job import MyJob\nregister_jobs(MyJob)"
692
- )
693
-
694
- with open(os.path.join(path, "jobs", "my_job.py"), "w") as fd:
695
- fd.write(
696
- "from nautobot.extras.jobs import Job\nclass MyJob(Job):\n def run(self):\n pass"
697
- )
698
-
699
- return mock.DEFAULT
700
-
701
- MockGitRepo.side_effect = populate_repo
702
- MockGitRepo.return_value.checkout.return_value = (self.COMMIT_HEXSHA, True)
703
-
507
+ self.repo.branch = "valid-files"
508
+ self.repo.save()
704
509
  # Run the Git operation and refresh the object from the DB
705
510
  job_model = GitRepositorySync().job_model
706
511
  job_result = run_job_for_testing(
@@ -717,13 +522,13 @@ class GitTest(TransactionTestCase):
717
522
 
718
523
  # Make sure ConfigContext was successfully loaded from file
719
524
  config_context = ConfigContext.objects.get(
720
- name="Region NYC servers",
525
+ name="Frobozz 1000 NTP servers",
721
526
  owner_object_id=self.repo.pk,
722
527
  owner_content_type=ContentType.objects.get_for_model(GitRepository),
723
528
  )
724
529
  self.assertIsNotNone(config_context)
725
530
  self.assertEqual(1500, config_context.weight)
726
- self.assertEqual("NTP servers for region NYC", config_context.description)
531
+ self.assertEqual("NTP servers for Frobozz 1000 devices **only**", config_context.description)
727
532
  self.assertTrue(config_context.is_active)
728
533
  self.assertEqual(
729
534
  {"ntp-servers": ["172.16.10.22", "172.16.10.33"]},
@@ -775,19 +580,13 @@ class GitTest(TransactionTestCase):
775
580
 
776
581
  self.assert_job_exists(installed=False)
777
582
 
778
- def test_git_dry_run(self, MockGitRepo):
583
+ def test_git_dry_run(self):
779
584
  with tempfile.TemporaryDirectory() as tempdir:
780
585
  with self.settings(GIT_ROOT=tempdir):
781
-
782
- def create_empty_repo(path, url, clone_initially=False):
783
- os.makedirs(path, exist_ok=True)
784
- return mock.DEFAULT
785
-
786
- MockGitRepo.side_effect = create_empty_repo
787
- MockGitRepo.return_value.checkout.return_value = (self.COMMIT_HEXSHA, False)
788
-
789
586
  self.mock_request.id = uuid.uuid4()
790
587
 
588
+ self.repo.branch = "valid-files"
589
+ self.repo.save()
791
590
  job_model = GitRepositoryDryRun().job_model
792
591
  job_result = run_job_for_testing(
793
592
  job=job_model,
@@ -801,14 +600,33 @@ class GitTest(TransactionTestCase):
801
600
  (job_result.traceback, list(job_result.job_log_entries.values_list("message", flat=True))),
802
601
  )
803
602
 
804
- MockGitRepo.assert_called_with(
805
- os.path.join(tempdir, self.repo.slug),
806
- self.repo.remote_url,
807
- clone_initially=False,
808
- )
809
- MockGitRepo.return_value.diff_remote.assert_called()
603
+ log_entries = JobLogEntry.objects.filter(job_result=job_result)
604
+
605
+ try:
606
+ log_entries.get(message__contains="Addition - `__init__.py`")
607
+ log_entries.get(message__contains="Addition - `config_context_schemas/schema-1.yaml`")
608
+ log_entries.get(message__contains="Addition - `config_contexts/context.yaml`")
609
+ log_entries.get(message__contains="Addition - `config_contexts/devices/test-device.json`")
610
+ log_entries.get(message__contains="Addition - `config_contexts/locations/Test Location.json`")
611
+ log_entries.get(message__contains="Addition - `export_templates/dcim/device/template.j2`")
612
+ log_entries.get(message__contains="Addition - `export_templates/dcim/device/template2.html`")
613
+ log_entries.get(message__contains="Addition - `export_templates/ipam/vlan/template.j2`")
614
+ log_entries.get(message__contains="Addition - `jobs/__init__.py`")
615
+ log_entries.get(message__contains="Addition - `jobs/my_job.py`")
616
+ except JobLogEntry.DoesNotExist:
617
+ for log in log_entries:
618
+ print(log.message)
619
+ raise
620
+
621
+ self.assertFalse(ConfigContextSchema.objects.filter(owner_object_id=self.repo.pk).exists())
622
+ self.assertFalse(ConfigContext.objects.filter(owner_object_id=self.repo.pk).exists())
623
+ self.assertFalse(ExportTemplate.objects.filter(owner_object_id=self.repo.pk).exists())
624
+ self.assertFalse(Job.objects.filter(module_name__startswith=self.repo.slug).exists())
625
+
626
+ # TODO: test dry-run against a branch name
627
+ # TODO: test dry-run against a specific commit hash
810
628
 
811
- def test_duplicate_repo_url_with_unique_provided_contents(self, MockGitRepo):
629
+ def test_duplicate_repo_url_with_unique_provided_contents(self):
812
630
  """Create a duplicate repo but with unique provided_contents."""
813
631
  remote_url = "http://localhost/duplicates.git"
814
632
  repo1 = GitRepository(
@@ -828,7 +646,7 @@ class GitTest(TransactionTestCase):
828
646
  repos = GitRepository.objects.filter(remote_url=remote_url)
829
647
  self.assertEqual(repos.count(), 2)
830
648
 
831
- def test_duplicate_repo_url_with_duplicate_provided_contents(self, MockGitRepo):
649
+ def test_duplicate_repo_url_with_duplicate_provided_contents(self):
832
650
  """Create a duplicate repo but with duplicate provided_contents."""
833
651
  remote_url = "http://localhost/duplicates.git"
834
652
  repo1 = GitRepository(