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
@@ -6,18 +6,19 @@ import os
6
6
  from django.conf import settings
7
7
  from django.core.exceptions import ValidationError
8
8
  from django.core.serializers.json import DjangoJSONEncoder
9
- from django.core.validators import URLValidator
10
9
  from django.db import models
11
10
 
12
11
  from nautobot.core.constants import CHARFIELD_MAX_LENGTH
13
- from nautobot.core.models.fields import AutoSlugField, slugify_dashes_to_underscores
12
+ from nautobot.core.models.fields import AutoSlugField, LaxURLField, slugify_dashes_to_underscores
14
13
  from nautobot.core.models.generics import PrimaryModel
14
+ from nautobot.core.models.validators import EnhancedURLValidator
15
15
  from nautobot.extras.utils import check_if_key_is_graphql_safe, extras_features
16
16
 
17
17
 
18
18
  @extras_features(
19
19
  "config_context_owners",
20
20
  "export_template_owners",
21
+ "graphql",
21
22
  "job_results",
22
23
  "webhooks",
23
24
  )
@@ -34,15 +35,16 @@ class GitRepository(PrimaryModel):
34
35
  slugify_function=slugify_dashes_to_underscores,
35
36
  )
36
37
 
37
- remote_url = models.URLField(
38
+ remote_url = LaxURLField(
38
39
  max_length=CHARFIELD_MAX_LENGTH,
39
40
  # For the moment we don't support ssh:// and git:// URLs
40
41
  help_text="Only HTTP and HTTPS URLs are presently supported",
41
- validators=[URLValidator(schemes=["http", "https"])],
42
+ validators=[EnhancedURLValidator(schemes=["http", "https"])],
42
43
  )
43
44
  branch = models.CharField(
44
45
  max_length=CHARFIELD_MAX_LENGTH,
45
46
  default="main",
47
+ help_text="Branch, tag, or commit",
46
48
  )
47
49
 
48
50
  current_head = models.CharField(
@@ -318,6 +318,25 @@ class Job(PrimaryModel):
318
318
  {"approval_required": "A job that may have sensitive variables cannot be marked as requiring approval"}
319
319
  )
320
320
 
321
+ def save(self, *args, **kwargs):
322
+ """When a Job is uninstalled, auto-disable all associated JobButtons, JobHooks, and ScheduledJobs."""
323
+ super().save(*args, **kwargs)
324
+ if not self.installed:
325
+ if self.is_job_button_receiver:
326
+ for jb in JobButton.objects.filter(job=self, enabled=True):
327
+ logger.info("Disabling JobButton %s derived from %s", jb, self)
328
+ jb.enabled = False
329
+ jb.save()
330
+ if self.is_job_hook_receiver:
331
+ for jh in JobHook.objects.filter(job=self, enabled=True):
332
+ logger.info("Disabling JobHook %s derived from %s", jh, self)
333
+ jh.enabled = False
334
+ jh.save()
335
+ for sj in ScheduledJob.objects.filter(job_model=self, enabled=True):
336
+ logger.info("Disabling ScheduledJob %s derived from %s", sj, self)
337
+ sj.enabled = False
338
+ sj.save()
339
+
321
340
 
322
341
  @extras_features("graphql")
323
342
  class JobHook(OrganizationalModel):
@@ -363,6 +382,9 @@ class JobHook(OrganizationalModel):
363
382
  if not self.type_create and not self.type_delete and not self.type_update:
364
383
  raise ValidationError("You must select at least one type: create, update, and/or delete.")
365
384
 
385
+ if self.enabled and not (self.job.installed and self.job.enabled):
386
+ raise ValidationError({"enabled": "The selected Job is not installed and enabled"})
387
+
366
388
  @classmethod
367
389
  def check_for_conflicts(
368
390
  cls, instance=None, content_types=None, job=None, type_create=None, type_update=None, type_delete=None
@@ -375,6 +397,7 @@ class JobHook(OrganizationalModel):
375
397
  """
376
398
 
377
399
  conflicts = {}
400
+
378
401
  job_hook_error_msg = "A job hook already exists for {action} on {content_type} to job {job}"
379
402
 
380
403
  if instance is not None and instance.present_in_database:
@@ -783,6 +806,7 @@ class JobButton(BaseModel, ChangeLoggedModel, NotesMixin):
783
806
  help_text="The object type(s) to which this job button applies.",
784
807
  )
785
808
  name = models.CharField(max_length=CHARFIELD_MAX_LENGTH, unique=True)
809
+ enabled = models.BooleanField(default=True)
786
810
  text = models.CharField(
787
811
  max_length=500,
788
812
  help_text="Jinja2 template code for button text. Reference the object as <code>{{ obj }}</code> such as <code>{{ obj.platform.name }}</code>. Buttons which render as empty text will not be displayed.",
@@ -817,6 +841,12 @@ class JobButton(BaseModel, ChangeLoggedModel, NotesMixin):
817
841
  def __str__(self):
818
842
  return self.name
819
843
 
844
+ def clean(self):
845
+ super().clean()
846
+
847
+ if self.enabled and not (self.job.installed and self.job.enabled):
848
+ raise ValidationError({"enabled": "The selected Job is not installed and enabled"})
849
+
820
850
 
821
851
  class ScheduledJobs(models.Model):
822
852
  """Helper table for tracking updates to scheduled tasks.
@@ -22,9 +22,8 @@ from rest_framework.utils.encoders import JSONEncoder
22
22
 
23
23
  from nautobot.core.constants import CHARFIELD_MAX_LENGTH
24
24
  from nautobot.core.models import BaseManager, BaseModel
25
- from nautobot.core.models.fields import ForeignKeyWithAutoRelatedName
25
+ from nautobot.core.models.fields import ForeignKeyWithAutoRelatedName, LaxURLField
26
26
  from nautobot.core.models.generics import OrganizationalModel, PrimaryModel
27
- from nautobot.core.models.validators import EnhancedURLValidator
28
27
  from nautobot.core.utils.data import deepmerge, render_jinja2
29
28
  from nautobot.extras.choices import (
30
29
  ButtonClassChoices,
@@ -442,10 +441,9 @@ class ExternalIntegration(PrimaryModel):
442
441
  """Model for tracking integrations with external applications."""
443
442
 
444
443
  name = models.CharField(max_length=CHARFIELD_MAX_LENGTH, unique=True)
445
- remote_url = models.CharField(
444
+ remote_url = LaxURLField(
446
445
  max_length=500,
447
446
  verbose_name="Remote URL",
448
- validators=[EnhancedURLValidator()],
449
447
  )
450
448
  secrets_group = models.ForeignKey(
451
449
  null=True,
@@ -246,7 +246,12 @@ def _handle_deleted_object(sender, instance, **kwargs):
246
246
 
247
247
  if save_new_objectchange:
248
248
  change_context.deferred_object_changes.setdefault(unique_object_change_id, []).append(
249
- {"action": ObjectChangeActionChoices.ACTION_DELETE, "instance": instance, "user": user}
249
+ {
250
+ "action": ObjectChangeActionChoices.ACTION_DELETE,
251
+ "instance": instance,
252
+ "user": user,
253
+ "changed_object_id": instance.pk,
254
+ }
250
255
  )
251
256
  if not change_context.defer_object_changes:
252
257
  objectchange = instance.to_objectchange(ObjectChangeActionChoices.ACTION_DELETE)
nautobot/extras/tables.py CHANGED
@@ -818,6 +818,7 @@ class JobButtonTable(BaseTable):
818
818
  pk = ToggleColumn()
819
819
  name = tables.Column(linkify=True)
820
820
  job = tables.Column(linkify=True)
821
+ enabled = BooleanColumn()
821
822
  confirmation = BooleanColumn()
822
823
  content_types = ContentTypesColumn(truncate_words=15)
823
824
 
@@ -829,6 +830,7 @@ class JobButtonTable(BaseTable):
829
830
  "content_types",
830
831
  "text",
831
832
  "job",
833
+ "enabled",
832
834
  "group_name",
833
835
  "weight",
834
836
  "button_class",
@@ -841,6 +843,7 @@ class JobButtonTable(BaseTable):
841
843
  "group_name",
842
844
  "weight",
843
845
  "job",
846
+ "enabled",
844
847
  "confirmation",
845
848
  )
846
849
 
@@ -32,19 +32,23 @@
32
32
  </tr>
33
33
  <tr>
34
34
  <td>Text</td>
35
- <td><span>{{ object.text }}</span></td>
35
+ <td><pre>{{ object.text }}</pre></td>
36
36
  </tr>
37
37
  <tr>
38
38
  <td>Job</td>
39
39
  <td><a href="{% url 'extras:job_run_by_class_path' class_path=object.job.class_path %}">{{ object.job }}</a></td>
40
40
  </tr>
41
+ <tr>
42
+ <td>Enabled</td>
43
+ <td>{{ object.enabled|render_boolean }}</td>
44
+ </tr>
41
45
  <tr>
42
46
  <td>Button Class</td>
43
47
  <td><button class="btn btn-{{ object.button_class }}">{{ object.button_class | title }}</button></td>
44
48
  </tr>
45
49
  <tr>
46
50
  <td>Confirmation</td>
47
- <td><span>{{ object.confirmation }}</span></td>
51
+ <td>{{ object.confirmation|render_boolean}}</td>
48
52
  </tr>
49
53
  </table>
50
54
  </div>
@@ -127,7 +127,7 @@ def _render_job_button_for_obj(job_button, obj, context, content_type):
127
127
  "object": obj,
128
128
  "job": job_button.job,
129
129
  "hidden_inputs": hidden_inputs,
130
- "disabled": "" if has_run_perm else "disabled",
130
+ "disabled": "" if (has_run_perm and job_button.job.installed and job_button.job.enabled) else "disabled",
131
131
  }
132
132
 
133
133
  if job_button.confirmation:
@@ -149,7 +149,7 @@ def job_buttons(context, obj):
149
149
  """
150
150
  content_type = ContentType.objects.get_for_model(obj)
151
151
  # We will enforce "run" permission later in deciding which buttons to show as disabled.
152
- buttons = JobButton.objects.filter(content_types=content_type)
152
+ buttons = JobButton.objects.filter(content_types=content_type, enabled=True)
153
153
  if not buttons:
154
154
  return SAFE_EMPTY_STR
155
155
 
@@ -0,0 +1,18 @@
1
+ ---
2
+ _metadata:
3
+ name: "Config Context Schema 1"
4
+ description: "Schema for defining first names, last names, and ages."
5
+ data_schema:
6
+ title: "Person"
7
+ type: "object"
8
+ properties:
9
+ firstName:
10
+ type: "string"
11
+ description: "The person's first name."
12
+ lastName:
13
+ type: "string"
14
+ description: "The person's last name."
15
+ age:
16
+ description: "Age in years which must be equal to or greater than zero."
17
+ type: "integer"
18
+ minimum: 0
@@ -0,0 +1,12 @@
1
+ ---
2
+ _metadata:
3
+ name: "Frobozz 1000 NTP servers"
4
+ weight: 1500
5
+ description: "NTP servers for Frobozz 1000 devices **only**"
6
+ is_active: true
7
+ config_context_schema: "Config Context Schema 1"
8
+ device_types:
9
+ - model: "Frobozz 1000"
10
+ ntp-servers:
11
+ - "172.16.10.22"
12
+ - "172.16.10.33"
@@ -0,0 +1,3 @@
1
+ {
2
+ "dns-servers": ["8.8.8.8"]
3
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "_metadata": {
3
+ "name": "Location context",
4
+ "is_active": false
5
+ },
6
+ "domain_name": "example.com"
7
+ }
@@ -0,0 +1,3 @@
1
+ {% for device in queryset %}
2
+ {{ device.name }}
3
+ {% endfor %}
@@ -0,0 +1,4 @@
1
+ <!DOCTYPE html>
2
+ {% for device in queryset %}
3
+ {{ device.name }}
4
+ {% endfor %}
@@ -0,0 +1,3 @@
1
+ {% for vlan in queryset %}
2
+ {{ vlan.name }}
3
+ {% endfor %}
@@ -0,0 +1,5 @@
1
+ from nautobot.core.celery import register_jobs
2
+
3
+ from .my_job import MyJob, MyJobButtonReceiver, MyJobHookReceiver
4
+
5
+ register_jobs(MyJob, MyJobButtonReceiver, MyJobHookReceiver)
@@ -0,0 +1,16 @@
1
+ from nautobot.extras.jobs import Job, JobButtonReceiver, JobHookReceiver
2
+
3
+
4
+ class MyJob(Job):
5
+ def run(self):
6
+ pass
7
+
8
+
9
+ class MyJobHookReceiver(JobHookReceiver):
10
+ def receive_job_hook(self, change, action, changed_object):
11
+ pass
12
+
13
+
14
+ class MyJobButtonReceiver(JobButtonReceiver):
15
+ def receive_job_button(self, obj):
16
+ pass
@@ -0,0 +1,3 @@
1
+ {% for nosuchmodel in queryset %}
2
+ {{ nosuchmodel.name }}
3
+ {% endfor %}
@@ -0,0 +1,3 @@
1
+ {% for device in queryset %}
2
+ {{ device.name }}
3
+ {% endfor %}
@@ -0,0 +1,2 @@
1
+ import .syntaxerror
2
+ import .importerror
@@ -0,0 +1 @@
1
+ import nosuchmodule
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env python
2
+
3
+ import logging
4
+ import os
5
+ import os.path
6
+ import shutil
7
+ import tempfile
8
+
9
+ from git import Repo
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ SOURCE_DIR = os.path.join(os.path.dirname(__file__), "git_data")
15
+
16
+
17
+ def create_and_populate_git_repository(target_path):
18
+ """
19
+ Create a Git repository in `target_path` and populate it with commits and tags based on contents of `SOURCE_DIR`.
20
+
21
+ An initial commit will always be created, containing no tracked files, and will be given the tag `empty-repo`.
22
+ After that, each subdir under `SOURCE_DIR` will be used as the basis for a single commit to the repo, so given:
23
+
24
+ nautobot/extras/tests/git_data
25
+ ├── 01-valid-files
26
+ │ ├── config_context_schemas
27
+ │ │ └── schema-1.yaml
28
+ │ └── config_contexts
29
+ │ └── context.yaml
30
+ └── 02-invalid-files
31
+ └── config_context_schemas
32
+ ├── badschema1.json
33
+ └── badschema2.json
34
+
35
+ ...after the initial empty commit, the next commit would contain files `config_context_schemas/schema-1.yaml` and
36
+ `config_contexts/context.yaml`, and would be given the tag `valid-files`. The next commit would remove those files
37
+ but add the files `config_context_schemas/badschema1.json` and `config_context_schemas/badschema2.json`, and would
38
+ be tagged as `invalid-files`.
39
+
40
+ Note that each commit is fully defined by the files in the appropriate subdirectory; if you want a file to exist
41
+ across multiple separate commits, it must exist in multiple subdirectories. Use of symlinks is encouraged in such
42
+ a scenario.
43
+ """
44
+ os.makedirs(target_path, exist_ok=True)
45
+ repo = Repo.init(target_path, initial_branch="main")
46
+ repo.config_writer().set_value("user", "name", "Nautobot Automation").release()
47
+ repo.config_writer().set_value("user", "email", "nautobot@nautobot.com").release()
48
+
49
+ repo.index.commit("Empty commit")
50
+ repo.create_tag("empty-repo", message="Nothing here yet")
51
+
52
+ # Create a commit matching each subdirectory in the git_data directory
53
+ for dirname in sorted(os.listdir(SOURCE_DIR)):
54
+ # Clean up from any previous commit
55
+ for root, _, files in os.walk(target_path):
56
+ if ".git" in root:
57
+ continue
58
+ for filename in files:
59
+ repo.index.remove([os.path.join(root, filename)])
60
+ os.remove(os.path.join(root, filename))
61
+
62
+ shutil.copytree(os.path.join(SOURCE_DIR, dirname), target_path, dirs_exist_ok=True)
63
+ for root, _, files in os.walk(target_path):
64
+ if ".git" in root:
65
+ continue
66
+ for filename in files:
67
+ repo.index.add([os.path.join(root, filename)])
68
+ repo.index.commit(dirname)
69
+ # Directory "01-valid-files" --> tag "valid-files" so that we won't break tests if we renumber the directories
70
+ repo.create_tag(dirname[3:], message=f"Tag based on {dirname} files")
71
+
72
+
73
+ if __name__ == "__main__":
74
+ directory_path = tempfile.TemporaryDirectory().name # pylint: disable=consider-using-with
75
+ print(f"Creating test Git repository in {directory_path}...")
76
+ create_and_populate_git_repository(directory_path)
@@ -17,6 +17,7 @@ from nautobot.core.models.fields import slugify_dashes_to_underscores
17
17
  from nautobot.core.testing import APITestCase, APIViewTestCases
18
18
  from nautobot.core.testing.utils import disable_warnings
19
19
  from nautobot.core.utils.lookup import get_route_for_model
20
+ from nautobot.core.utils.permissions import get_permission_for_model
20
21
  from nautobot.dcim.models import (
21
22
  Controller,
22
23
  Device,
@@ -768,7 +769,7 @@ class DynamicGroupTestMixin:
768
769
 
769
770
  # Then the DynamicGroups.
770
771
  cls.content_type = ContentType.objects.get_for_model(Device)
771
- cls.groups = cls.groups = [
772
+ cls.groups = [
772
773
  DynamicGroup.objects.create(
773
774
  name="API DynamicGroup 1",
774
775
  content_type=cls.content_type,
@@ -811,13 +812,34 @@ class DynamicGroupTest(DynamicGroupTestMixin, APIViewTestCases.APIViewTestCase):
811
812
  def test_get_members(self):
812
813
  """Test that the `/members/` API endpoint returns what is expected."""
813
814
  self.add_permissions("extras.view_dynamicgroup")
814
- instance = DynamicGroup.objects.first()
815
+ instance = self.groups[0]
816
+ self.add_permissions(get_permission_for_model(instance.content_type.model_class(), "view"))
815
817
  member_count = instance.members.count()
816
818
  url = reverse("extras-api:dynamicgroup-members", kwargs={"pk": instance.pk})
817
819
  response = self.client.get(url, **self.header)
818
820
  self.assertHttpStatus(response, status.HTTP_200_OK)
819
821
  self.assertEqual(member_count, len(response.json()["results"]))
820
822
 
823
+ def test_get_members_with_constrained_permission(self):
824
+ """Test that the `/members/` API endpoint enforces permissions on the member model."""
825
+ self.add_permissions("extras.view_dynamicgroup")
826
+ instance = self.groups[0]
827
+ obj1 = instance.members.first()
828
+ obj_perm = ObjectPermission(
829
+ name="Test permission",
830
+ constraints={"pk__in": [obj1.pk]},
831
+ actions=["view"],
832
+ )
833
+ obj_perm.save()
834
+ obj_perm.users.add(self.user)
835
+ obj_perm.object_types.add(instance.content_type)
836
+
837
+ url = reverse("extras-api:dynamicgroup-members", kwargs={"pk": instance.pk})
838
+ response = self.client.get(url, **self.header)
839
+ self.assertHttpStatus(response, status.HTTP_200_OK)
840
+ self.assertEqual(len(response.json()["results"]), 1)
841
+ self.assertEqual(response.json()["results"][0]["id"], str(obj1.pk))
842
+
821
843
 
822
844
  class DynamicGroupMembershipTest(DynamicGroupTestMixin, APIViewTestCases.APIViewTestCase):
823
845
  model = DynamicGroupMembership
@@ -2023,46 +2045,56 @@ class JobHookTest(APIViewTestCases.APIViewTestCase):
2023
2045
 
2024
2046
  @classmethod
2025
2047
  def setUpTestData(cls):
2048
+ jhr_log = Job.objects.get(job_class_name="TestJobHookReceiverLog")
2049
+ jhr_log.enabled = True
2050
+ jhr_log.save()
2051
+ jhr_change = Job.objects.get(job_class_name="TestJobHookReceiverChange")
2052
+ jhr_change.enabled = True
2053
+ jhr_change.save()
2054
+ jhr_fail = Job.objects.get(job_class_name="TestJobHookReceiverFail")
2055
+ jhr_fail.enabled = True
2056
+ jhr_fail.save()
2057
+
2026
2058
  cls.create_data = [
2027
2059
  {
2028
2060
  "name": "JobHook4",
2029
2061
  "content_types": ["dcim.consoleport"],
2030
2062
  "type_delete": True,
2031
- "job": Job.objects.get(job_class_name="TestJobHookReceiverLog").pk,
2063
+ "job": jhr_log.pk,
2032
2064
  "enabled": False,
2033
2065
  },
2034
2066
  {
2035
2067
  "name": "JobHook5",
2036
2068
  "content_types": ["dcim.consoleport"],
2037
2069
  "type_delete": True,
2038
- "job": Job.objects.get(job_class_name="TestJobHookReceiverChange").pk,
2070
+ "job": jhr_change.pk,
2039
2071
  "enabled": False,
2040
2072
  },
2041
2073
  {
2042
2074
  "name": "JobHook6",
2043
2075
  "content_types": ["dcim.consoleport"],
2044
2076
  "type_delete": True,
2045
- "job": Job.objects.get(job_class_name="TestJobHookReceiverFail").pk,
2077
+ "job": jhr_fail.pk,
2046
2078
  "enabled": False,
2047
2079
  },
2048
2080
  ]
2049
2081
  cls.job_hooks = (
2050
2082
  JobHook(
2051
2083
  name="JobHook1",
2084
+ job=jhr_log,
2052
2085
  type_create=True,
2053
- job=Job.objects.get(job_class_name="TestJobHookReceiverLog"),
2054
2086
  type_delete=True,
2055
2087
  ),
2056
2088
  JobHook(
2057
2089
  name="JobHook2",
2090
+ job=jhr_change,
2058
2091
  type_create=True,
2059
- job=Job.objects.get(job_class_name="TestJobHookReceiverChange"),
2060
2092
  type_delete=True,
2061
2093
  ),
2062
2094
  JobHook(
2063
2095
  name="JobHook3",
2096
+ job=jhr_fail,
2064
2097
  type_create=True,
2065
- job=Job.objects.get(job_class_name="TestJobHookReceiverFail"),
2066
2098
  type_delete=True,
2067
2099
  ),
2068
2100
  )
@@ -2116,18 +2148,25 @@ class JobButtonTest(APIViewTestCases.APIViewTestCase):
2116
2148
 
2117
2149
  @classmethod
2118
2150
  def setUpTestData(cls):
2151
+ jbr_simple = Job.objects.get(job_class_name="TestJobButtonReceiverSimple")
2152
+ jbr_simple.enabled = True
2153
+ jbr_simple.save()
2154
+ jbr_complex = Job.objects.get(job_class_name="TestJobButtonReceiverComplex")
2155
+ jbr_complex.enabled = True
2156
+ jbr_complex.save()
2157
+
2119
2158
  cls.create_data = [
2120
2159
  {
2121
2160
  "name": "JobButton4",
2122
2161
  "text": "JobButton4",
2123
2162
  "content_types": ["dcim.location"],
2124
- "job": Job.objects.get(job_class_name="TestJobButtonReceiverSimple").pk,
2163
+ "job": jbr_simple.pk,
2125
2164
  },
2126
2165
  {
2127
2166
  "name": "JobButton5",
2128
2167
  "text": "JobButton5",
2129
2168
  "content_types": ["circuits.circuit"],
2130
- "job": Job.objects.get(job_class_name="TestJobButtonReceiverComplex").pk,
2169
+ "job": jbr_complex.pk,
2131
2170
  },
2132
2171
  ]
2133
2172
  location_type = ContentType.objects.get_for_model(Location)
@@ -2136,7 +2175,7 @@ class JobButtonTest(APIViewTestCases.APIViewTestCase):
2136
2175
  location_jb = JobButton(
2137
2176
  name="api-test-location",
2138
2177
  text="API job button location text",
2139
- job=Job.objects.get(job_class_name="TestJobButtonReceiverSimple"),
2178
+ job=jbr_simple,
2140
2179
  weight=100,
2141
2180
  confirmation=True,
2142
2181
  )
@@ -2146,7 +2185,7 @@ class JobButtonTest(APIViewTestCases.APIViewTestCase):
2146
2185
  device_jb = JobButton.objects.create(
2147
2186
  name="api-test-device",
2148
2187
  text="API job button device text",
2149
- job=Job.objects.get(job_class_name="TestJobButtonReceiverSimple"),
2188
+ job=jbr_simple,
2150
2189
  weight=100,
2151
2190
  confirmation=True,
2152
2191
  )
@@ -2156,7 +2195,7 @@ class JobButtonTest(APIViewTestCases.APIViewTestCase):
2156
2195
  complex_jb = JobButton.objects.create(
2157
2196
  name="api-test-complex",
2158
2197
  text="API job button complex text",
2159
- job=Job.objects.get(job_class_name="TestJobButtonReceiverComplex"),
2198
+ job=jbr_complex,
2160
2199
  weight=100,
2161
2200
  confirmation=True,
2162
2201
  )
@@ -5,7 +5,16 @@ from django.test import TestCase
5
5
  from nautobot.core.celery import app
6
6
  from nautobot.core.testing import TransactionTestCase
7
7
  from nautobot.core.utils.lookup import get_changes_for_model
8
- from nautobot.dcim.models import Location, LocationType
8
+ from nautobot.dcim.models import (
9
+ DeviceType,
10
+ DeviceTypeToSoftwareImageFile,
11
+ Location,
12
+ LocationType,
13
+ Manufacturer,
14
+ Platform,
15
+ SoftwareImageFile,
16
+ SoftwareVersion,
17
+ )
9
18
  from nautobot.extras.choices import ObjectChangeActionChoices, ObjectChangeEventContextChoices
10
19
  from nautobot.extras.context_managers import (
11
20
  deferred_change_logging_for_bulk_operation,
@@ -294,6 +303,29 @@ class BulkEditDeleteChangeLogging(TestCase):
294
303
  self.assertIsNone(snapshots["differences"]["removed"])
295
304
  self.assertEqual(snapshots["differences"]["added"]["description"], "changed")
296
305
 
306
+ def test_bulk_edit_device_type_software_image_file(self):
307
+ """Test that bulk edits to null does not cause integrity error"""
308
+ manufacturer = Manufacturer.objects.create(name="Test")
309
+ platform = Platform.objects.create(name="Test")
310
+ software_status = Status.objects.get_for_model(SoftwareVersion).first()
311
+ software_version = SoftwareVersion.objects.create(version="1.0.0", platform=platform, status=software_status)
312
+ software_image_file = SoftwareImageFile.objects.create(
313
+ image_file_name="test.iso", software_version=software_version, status=software_status
314
+ )
315
+ device_type = DeviceType.objects.create(manufacturer=manufacturer, model="test123")
316
+ device_type.software_image_files.set([software_image_file])
317
+ with web_request_context(self.user):
318
+ with deferred_change_logging_for_bulk_operation():
319
+ device_type.software_image_files.set([])
320
+ device_type.save()
321
+
322
+ oc_list = get_changes_for_model(DeviceTypeToSoftwareImageFile)
323
+ self.assertEqual(len(oc_list), 1)
324
+ self.assertEqual(oc_list[0].action, ObjectChangeActionChoices.ACTION_DELETE)
325
+ self.assertIsNotNone(oc_list[0].changed_object_id)
326
+ self.assertEqual(oc_list[0].user, self.user)
327
+ self.assertEqual(oc_list[0].user_name, self.user.username)
328
+
297
329
  def test_change_log_context(self):
298
330
  location_type = LocationType.objects.get(name="Campus")
299
331
  location_status = Status.objects.get_for_model(Location).first()