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
@@ -304,13 +304,13 @@ class DynamicGroupViewSet(NotesViewSetMixin, ModelViewSet):
304
304
  # @extend_schema(methods=["get"], responses={200: member_response})
305
305
  @action(detail=True, methods=["get"])
306
306
  def members(self, request, pk, *args, **kwargs):
307
- """List member objects of the same type as the `content_type` for this dynamic group."""
307
+ """List the member objects of this dynamic group."""
308
308
  instance = get_object_or_404(self.queryset, pk=pk)
309
309
 
310
310
  # Retrieve the serializer for the content_type and paginate the results
311
311
  member_model_class = instance.content_type.model_class()
312
312
  member_serializer_class = get_serializer_for_model(member_model_class)
313
- members = self.paginate_queryset(instance.members)
313
+ members = self.paginate_queryset(instance.members.restrict(request.user, "view"))
314
314
  member_serializer = member_serializer_class(members, many=True, context={"request": request})
315
315
  return self.get_paginated_response(member_serializer.data)
316
316
 
@@ -91,9 +91,12 @@ class ChangeContext:
91
91
  for entry in self.deferred_object_changes[key]:
92
92
  objectchange = entry["instance"].to_objectchange(entry["action"])
93
93
  objectchange.user = entry["user"]
94
+ objectchange.user_name = objectchange.user.username
94
95
  objectchange.request_id = self.change_id
95
96
  objectchange.change_context = self.context
96
97
  objectchange.change_context_detail = self.context_detail[:CHANGELOG_MAX_CHANGE_CONTEXT_DETAIL]
98
+ if not objectchange.changed_object_id:
99
+ objectchange.changed_object_id = entry.get("changed_object_id")
97
100
  create_object_changes.append(objectchange)
98
101
  self.deferred_object_changes.pop(key, None)
99
102
  ObjectChange.objects.bulk_create(create_object_changes, batch_size=batch_size)
@@ -136,7 +136,7 @@ def get_repo_from_url_to_path_and_from_branch(repository_record):
136
136
 
137
137
  def ensure_git_repository(repository_record, logger=None, head=None): # pylint: disable=redefined-outer-name
138
138
  """Ensure that the given Git repo is present, up-to-date, and has the correct branch selected.
139
- Note that this function may be called independently of the `GitRepositoryiSync` job,
139
+ Note that this function may be called independently of the `GitRepositorySync` job,
140
140
  such as to ensure that different Nautobot instances and/or worker instances all have a local copy of the same HEAD.
141
141
  Args:
142
142
  repository_record (GitRepository): Repository to ensure the state of.
@@ -224,126 +224,127 @@ def refresh_git_config_contexts(repository_record, job_result, delete=False):
224
224
  def update_git_config_contexts(repository_record, job_result):
225
225
  """Refresh any config contexts provided by this Git repository."""
226
226
  config_context_path = os.path.join(repository_record.filesystem_path, "config_contexts")
227
- if not os.path.isdir(config_context_path):
228
- return
229
-
230
227
  managed_config_contexts = set()
231
228
  managed_local_config_contexts = defaultdict(set)
232
229
 
233
- # First, handle the "flat file" case - data files in the root config_context_path,
234
- # whose metadata is expressed purely within the contents of the file:
235
- for file_name in os.listdir(config_context_path):
236
- if not os.path.isfile(os.path.join(config_context_path, file_name)):
237
- continue
238
- msg = f"Loading config context from `{file_name}`"
239
- logger.info(msg)
240
- job_result.log(msg, grouping="config contexts")
241
- try:
242
- with open(os.path.join(config_context_path, file_name), "r") as fd:
243
- # The data file can be either JSON or YAML; since YAML is a superset of JSON, we can load it regardless
244
- context_data = yaml.safe_load(fd)
245
-
246
- # A file can contain one config context dict or a list thereof
247
- if isinstance(context_data, dict):
248
- context_name = import_config_context(context_data, repository_record, job_result)
249
- managed_config_contexts.add(context_name)
250
- elif isinstance(context_data, list):
251
- for context_data_entry in context_data:
252
- context_name = import_config_context(context_data_entry, repository_record, job_result)
253
- managed_config_contexts.add(context_name)
254
- else:
255
- raise RuntimeError("data must be a dict or list of dicts")
256
-
257
- except Exception as exc:
258
- msg = f"Error in loading config context data from `{file_name}`: {exc}"
259
- logger.error(msg)
260
- job_result.log(msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="config contexts")
261
-
262
- # Next, handle the "filter/name" directory structure case - files in <filter_type>/<name>.(json|yaml)
263
- for filter_type in (
264
- "locations",
265
- "device_types",
266
- "roles",
267
- "platforms",
268
- "cluster_groups",
269
- "clusters",
270
- "tenant_groups",
271
- "tenants",
272
- "tags",
273
- "dynamic_groups",
274
- "device_redundancy_groups",
275
- ):
276
- if os.path.isdir(os.path.join(repository_record.filesystem_path, filter_type)):
277
- msg = (
278
- f'Found "{filter_type}" directory in the repository root. If this is meant to contain config contexts, '
279
- "it should be moved into a `config_contexts/` subdirectory."
280
- )
281
- logger.warning(msg)
282
- job_result.log(msg, level_choice=LogLevelChoices.LOG_WARNING, grouping="config contexts")
283
-
284
- dir_path = os.path.join(config_context_path, filter_type)
285
- if not os.path.isdir(dir_path):
286
- continue
287
-
288
- for file_name in os.listdir(dir_path):
289
- name = os.path.splitext(file_name)[0]
290
- msg = f'Loading config context, filter `{filter_type} = [name: "{name}"]`, from `{filter_type}/{file_name}`'
230
+ if os.path.isdir(config_context_path):
231
+ # First, handle the "flat file" case - data files in the root config_context_path,
232
+ # whose metadata is expressed purely within the contents of the file:
233
+ for file_name in os.listdir(config_context_path):
234
+ if not os.path.isfile(os.path.join(config_context_path, file_name)):
235
+ continue
236
+ msg = f"Loading config context from `{file_name}`"
291
237
  logger.info(msg)
292
238
  job_result.log(msg, grouping="config contexts")
293
239
  try:
294
- with open(os.path.join(dir_path, file_name), "r") as fd:
295
- # Data file can be either JSON or YAML; since YAML is a superset of JSON, we can load it regardless
240
+ with open(os.path.join(config_context_path, file_name), "r") as fd:
241
+ # The data file can be either JSON or YAML; since YAML is a superset of JSON, we load it regardless
296
242
  context_data = yaml.safe_load(fd)
297
243
 
298
- # Unlike the above case, these files always contain just a single config context record
299
-
300
- # Add the implied filter to the context metadata
301
- if filter_type == "device_types":
302
- context_data.setdefault("_metadata", {}).setdefault(filter_type, []).append({"model": name})
244
+ # A file can contain one config context dict or a list thereof
245
+ if isinstance(context_data, dict):
246
+ context_name = import_config_context(context_data, repository_record, job_result)
247
+ managed_config_contexts.add(context_name)
248
+ elif isinstance(context_data, list):
249
+ for context_data_entry in context_data:
250
+ context_name = import_config_context(context_data_entry, repository_record, job_result)
251
+ managed_config_contexts.add(context_name)
303
252
  else:
304
- context_data.setdefault("_metadata", {}).setdefault(filter_type, []).append({"name": name})
253
+ raise RuntimeError("data must be a dict or list of dicts")
305
254
 
306
- context_name = import_config_context(context_data, repository_record, job_result)
307
- managed_config_contexts.add(context_name)
308
255
  except Exception as exc:
309
256
  msg = f"Error in loading config context data from `{file_name}`: {exc}"
310
257
  logger.error(msg)
311
258
  job_result.log(msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="config contexts")
312
259
 
313
- # Finally, handle device- and virtual-machine-specific "local" context in (devices|virtual_machines)/<name>.(json|yaml)
314
- for local_type in ("devices", "virtual_machines"):
315
- if os.path.isdir(os.path.join(repository_record.filesystem_path, local_type)):
316
- msg = (
317
- f'Found "{local_type}" directory in the repository root. If this is meant to contain config contexts, '
318
- "it should be moved into a `config_contexts/` subdirectory."
319
- )
320
- logger.warning(msg)
321
- job_result.log(msg, level_choice=LogLevelChoices.LOG_WARNING, grouping="config contexts")
260
+ # Next, handle the "filter/name" directory structure case - files in <filter_type>/<name>.(json|yaml)
261
+ for filter_type in (
262
+ "locations",
263
+ "device_types",
264
+ "roles",
265
+ "platforms",
266
+ "cluster_groups",
267
+ "clusters",
268
+ "tenant_groups",
269
+ "tenants",
270
+ "tags",
271
+ "dynamic_groups",
272
+ "device_redundancy_groups",
273
+ ):
274
+ if os.path.isdir(os.path.join(repository_record.filesystem_path, filter_type)):
275
+ msg = (
276
+ f'Found "{filter_type}" directory in the repository root. If this is meant to contain config contexts, '
277
+ "it should be moved into a `config_contexts/` subdirectory."
278
+ )
279
+ logger.warning(msg)
280
+ job_result.log(msg, level_choice=LogLevelChoices.LOG_WARNING, grouping="config contexts")
322
281
 
323
- dir_path = os.path.join(config_context_path, local_type)
324
- if not os.path.isdir(dir_path):
325
- continue
282
+ dir_path = os.path.join(config_context_path, filter_type)
283
+ if not os.path.isdir(dir_path):
284
+ continue
326
285
 
327
- for file_name in os.listdir(dir_path):
328
- device_name = os.path.splitext(file_name)[0]
329
- msg = f"Loading local config context for `{device_name}` from `{local_type}/{file_name}`"
330
- logger.info(msg)
331
- job_result.log(msg, grouping="local config contexts")
332
- try:
333
- with open(os.path.join(dir_path, file_name), "r") as fd:
334
- context_data = yaml.safe_load(fd)
286
+ for file_name in os.listdir(dir_path):
287
+ name = os.path.splitext(file_name)[0]
288
+ msg = (
289
+ f'Loading config context, filter `{filter_type} = [name: "{name}"]`, '
290
+ f"from `{filter_type}/{file_name}`"
291
+ )
292
+ logger.info(msg)
293
+ job_result.log(msg, grouping="config contexts")
294
+ try:
295
+ with open(os.path.join(dir_path, file_name), "r") as fd:
296
+ # Data file can be either JSON or YAML; since YAML is a superset of JSON, we load it regardless
297
+ context_data = yaml.safe_load(fd)
298
+
299
+ # Unlike the above case, these files always contain just a single config context record
300
+
301
+ # Add the implied filter to the context metadata
302
+ if filter_type == "device_types":
303
+ context_data.setdefault("_metadata", {}).setdefault(filter_type, []).append({"model": name})
304
+ else:
305
+ context_data.setdefault("_metadata", {}).setdefault(filter_type, []).append({"name": name})
335
306
 
336
- import_local_config_context(
337
- local_type,
338
- device_name,
339
- context_data,
340
- repository_record,
307
+ context_name = import_config_context(context_data, repository_record, job_result)
308
+ managed_config_contexts.add(context_name)
309
+ except Exception as exc:
310
+ msg = f"Error in loading config context data from `{file_name}`: {exc}"
311
+ logger.error(msg)
312
+ job_result.log(msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="config contexts")
313
+
314
+ # Finally, handle device- and VM-specific "local" context in (devices|virtual_machines)/<name>.(json|yaml)
315
+ for local_type in ("devices", "virtual_machines"):
316
+ if os.path.isdir(os.path.join(repository_record.filesystem_path, local_type)):
317
+ msg = (
318
+ f'Found "{local_type}" directory in the repository root. If this is meant to contain '
319
+ "config contexts, it should be moved into a `config_contexts/` subdirectory."
341
320
  )
342
- managed_local_config_contexts[local_type].add(device_name)
343
- except Exception as exc:
344
- msg = f"Error in loading local config context from `{local_type}/{file_name}`: {exc}"
345
- logger.error(msg)
346
- job_result.log(msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="local config contexts")
321
+ logger.warning(msg)
322
+ job_result.log(msg, level_choice=LogLevelChoices.LOG_WARNING, grouping="config contexts")
323
+
324
+ dir_path = os.path.join(config_context_path, local_type)
325
+ if not os.path.isdir(dir_path):
326
+ continue
327
+
328
+ for file_name in os.listdir(dir_path):
329
+ device_name = os.path.splitext(file_name)[0]
330
+ msg = f"Loading local config context for `{device_name}` from `{local_type}/{file_name}`"
331
+ logger.info(msg)
332
+ job_result.log(msg, grouping="local config contexts")
333
+ try:
334
+ with open(os.path.join(dir_path, file_name), "r") as fd:
335
+ context_data = yaml.safe_load(fd)
336
+
337
+ import_local_config_context(
338
+ local_type,
339
+ device_name,
340
+ context_data,
341
+ repository_record,
342
+ )
343
+ managed_local_config_contexts[local_type].add(device_name)
344
+ except Exception as exc:
345
+ msg = f"Error in loading local config context from `{local_type}/{file_name}`: {exc}"
346
+ logger.error(msg)
347
+ job_result.log(msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="local config contexts")
347
348
 
348
349
  # Delete any prior contexts that are owned by this repository but were not created/updated above
349
350
  delete_git_config_contexts(
@@ -599,39 +600,38 @@ def refresh_git_config_context_schemas(repository_record, job_result, delete=Fal
599
600
  def update_git_config_context_schemas(repository_record, job_result):
600
601
  """Refresh any config context schemas provided by this Git repository."""
601
602
  config_context_schema_path = os.path.join(repository_record.filesystem_path, "config_context_schemas")
602
- if not os.path.isdir(config_context_schema_path):
603
- return
604
603
 
605
604
  managed_config_context_schemas = set()
606
605
 
607
- for file_name in os.listdir(config_context_schema_path):
608
- if not os.path.isfile(os.path.join(config_context_schema_path, file_name)):
609
- continue
610
- msg = (f"Loading config context schema from `{file_name}`",)
611
- logger.info(msg)
612
- job_result.log(msg, grouping="config context schemas")
613
- try:
614
- with open(os.path.join(config_context_schema_path, file_name), "r") as fd:
615
- # The data file can be either JSON or YAML; since YAML is a superset of JSON, we can load it regardless
616
- context_schema_data = yaml.safe_load(fd)
617
-
618
- # A file can contain one config context dict or a list thereof
619
- if isinstance(context_schema_data, dict):
620
- context_name = import_config_context_schema(context_schema_data, repository_record, job_result)
621
- managed_config_context_schemas.add(context_name)
622
- elif isinstance(context_schema_data, list):
623
- for context_schema in context_schema_data:
624
- if isinstance(context_schema, dict):
625
- context_name = import_config_context_schema(context_schema, repository_record, job_result)
626
- managed_config_context_schemas.add(context_name)
627
- else:
628
- raise RuntimeError("each item in list data must be a dict")
629
- else:
630
- raise RuntimeError("data must be a dict or a list of dicts")
631
- except Exception as exc:
632
- msg = f"Error in loading config context schema data from `{file_name}`: {exc}"
633
- logger.error(msg)
634
- job_result.log(msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="config context schemas")
606
+ if os.path.isdir(config_context_schema_path):
607
+ for file_name in os.listdir(config_context_schema_path):
608
+ if not os.path.isfile(os.path.join(config_context_schema_path, file_name)):
609
+ continue
610
+ msg = (f"Loading config context schema from `{file_name}`",)
611
+ logger.info(msg)
612
+ job_result.log(msg, grouping="config context schemas")
613
+ try:
614
+ with open(os.path.join(config_context_schema_path, file_name), "r") as fd:
615
+ # The data file can be either JSON or YAML; since YAML is a superset of JSON, we load it regardless
616
+ context_schema_data = yaml.safe_load(fd)
617
+
618
+ # A file can contain one config context dict or a list thereof
619
+ if isinstance(context_schema_data, dict):
620
+ context_name = import_config_context_schema(context_schema_data, repository_record, job_result)
621
+ managed_config_context_schemas.add(context_name)
622
+ elif isinstance(context_schema_data, list):
623
+ for context_schema in context_schema_data:
624
+ if isinstance(context_schema, dict):
625
+ context_name = import_config_context_schema(context_schema, repository_record, job_result)
626
+ managed_config_context_schemas.add(context_name)
627
+ else:
628
+ raise RuntimeError("each item in list data must be a dict")
629
+ else:
630
+ raise RuntimeError("data must be a dict or a list of dicts")
631
+ except Exception as exc:
632
+ msg = f"Error in loading config context schema data from `{file_name}`: {exc}"
633
+ logger.error(msg)
634
+ job_result.log(msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="config context schemas")
635
635
 
636
636
  # Delete any prior contexts that are owned by this repository but were not created/updated above
637
637
  delete_git_config_context_schemas(
@@ -835,12 +835,10 @@ def update_git_export_templates(repository_record, job_result):
835
835
  job_result.log(msg, level_choice=LogLevelChoices.LOG_WARNING, grouping="export templates")
836
836
 
837
837
  export_template_path = os.path.join(repository_record.filesystem_path, "export_templates")
838
- if not os.path.isdir(export_template_path):
839
- return
838
+ managed_export_templates = {}
840
839
 
841
840
  git_repository_content_type = ContentType.objects.get_for_model(GitRepository)
842
841
 
843
- managed_export_templates = {}
844
842
  for model_content_type, file_path in files_from_contenttype_directories(
845
843
  export_template_path, job_result, "export templates"
846
844
  ):
@@ -15,6 +15,9 @@ def files_from_contenttype_directories(base_path, job_result, log_grouping):
15
15
  Returns:
16
16
  (Tuple[ContentType, file_path]): A tuple of the ContentType and the file path.
17
17
  """
18
+ if not os.path.isdir(base_path):
19
+ return
20
+
18
21
  for app_label in os.listdir(base_path):
19
22
  app_label_path = os.path.join(base_path, app_label)
20
23
  if not os.path.isdir(app_label_path):
@@ -504,7 +504,7 @@ class ContactFilterSet(ContactTeamFilterSet):
504
504
  fields = "__all__"
505
505
 
506
506
 
507
- class ContactAssociationFilterSet(NautobotFilterSet):
507
+ class ContactAssociationFilterSet(NautobotFilterSet, StatusModelFilterSetMixin, RoleModelFilterSetMixin):
508
508
  q = SearchFilter(
509
509
  filter_predicates={
510
510
  "contact__name": "icontains",
@@ -512,6 +512,19 @@ class ContactAssociationFilterSet(NautobotFilterSet):
512
512
  },
513
513
  )
514
514
 
515
+ contact = NaturalKeyOrPKMultipleChoiceFilter(
516
+ queryset=Contact.objects.all(),
517
+ to_field_name="name",
518
+ label="Contact (name or ID)",
519
+ )
520
+ team = NaturalKeyOrPKMultipleChoiceFilter(
521
+ queryset=Team.objects.all(),
522
+ to_field_name="name",
523
+ label="Team (name or ID)",
524
+ )
525
+
526
+ associated_object_type = ContentTypeFilter()
527
+
515
528
  class Meta:
516
529
  model = ContactAssociation
517
530
  fields = "__all__"
@@ -610,6 +623,7 @@ class ExportTemplateFilterSet(BaseFilterSet):
610
623
  },
611
624
  )
612
625
  owner_content_type = ContentTypeFilter()
626
+ content_type = ContentTypeFilter()
613
627
 
614
628
  class Meta:
615
629
  model = ExportTemplate
@@ -888,6 +902,7 @@ class JobButtonFilterSet(BaseFilterSet):
888
902
  fields = (
889
903
  "content_types",
890
904
  "name",
905
+ "enabled",
891
906
  "text",
892
907
  "job",
893
908
  "weight",
@@ -27,6 +27,7 @@ from nautobot.core.forms import (
27
27
  DynamicModelMultipleChoiceField,
28
28
  JSONArrayFormField,
29
29
  JSONField,
30
+ LaxURLField,
30
31
  MultipleContentTypeField,
31
32
  SlugField,
32
33
  StaticSelect2,
@@ -104,6 +105,7 @@ __all__ = (
104
105
  "ConfigContextSchemaBulkEditForm",
105
106
  "ConfigContextSchemaFilterForm",
106
107
  "CustomFieldForm",
108
+ "CustomFieldFilterForm",
107
109
  "CustomFieldModelCSVForm",
108
110
  "CustomFieldBulkCreateForm", # 2.0 TODO remove this deprecated class
109
111
  "CustomFieldChoiceFormSet",
@@ -114,6 +116,7 @@ __all__ = (
114
116
  "DynamicGroupMembershipFormSet",
115
117
  "ExportTemplateForm",
116
118
  "ExportTemplateFilterForm",
119
+ "ExternalIntegrationFilterForm",
117
120
  "ExternalIntegrationForm",
118
121
  "ExternalIntegrationBulkEditForm",
119
122
  "GitRepositoryForm",
@@ -144,6 +147,7 @@ __all__ = (
144
147
  "RelationshipFilterForm",
145
148
  "RelationshipAssociationFilterForm",
146
149
  "RoleBulkEditForm",
150
+ "RoleFilterForm",
147
151
  "RoleForm",
148
152
  "ScheduledJobFilterForm",
149
153
  "SecretForm",
@@ -421,6 +425,17 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
421
425
  self.fields["key"].widget.attrs["readonly"] = True
422
426
 
423
427
 
428
+ class CustomFieldFilterForm(NautobotFilterForm):
429
+ model = CustomField
430
+ q = forms.CharField(required=False, label="Search")
431
+ content_types = MultipleContentTypeField(
432
+ queryset=ContentType.objects.filter(FeatureQuery("custom_fields").get_query()),
433
+ choices_as_strings=True,
434
+ required=False,
435
+ label="Content Type(s)",
436
+ )
437
+
438
+
424
439
  class CustomFieldModelCSVForm(CSVModelForm, CustomFieldModelFormMixin):
425
440
  """
426
441
  Base class for CSV/JSON/YAML import of models that support custom fields.
@@ -638,6 +653,14 @@ class ExternalIntegrationBulkEditForm(NautobotBulkEditForm):
638
653
  nullable_fields = ["extra_config", "secrets_group", "headers"]
639
654
 
640
655
 
656
+ class ExternalIntegrationFilterForm(NautobotFilterForm):
657
+ model = ExternalIntegration
658
+ q = forms.CharField(required=False, label="Search")
659
+ secrets_group = DynamicModelMultipleChoiceField(
660
+ queryset=SecretsGroup.objects.all(), to_field_name="name", required=False
661
+ )
662
+
663
+
641
664
  #
642
665
  # Git repositories and other data sources
643
666
  #
@@ -665,7 +688,7 @@ class PasswordInputWithPlaceholder(forms.PasswordInput):
665
688
  class GitRepositoryForm(NautobotModelForm):
666
689
  slug = SlugField(help_text="Filesystem-friendly unique shorthand")
667
690
 
668
- remote_url = forms.URLField(
691
+ remote_url = LaxURLField(
669
692
  required=True,
670
693
  label="Remote URL",
671
694
  help_text="Only http:// and https:// URLs are presently supported",
@@ -716,7 +739,7 @@ class GitRepositoryBulkEditForm(NautobotBulkEditForm):
716
739
  queryset=GitRepository.objects.all(),
717
740
  widget=forms.MultipleHiddenInput(),
718
741
  )
719
- remote_url = forms.CharField(
742
+ remote_url = LaxURLField(
720
743
  label="Remote URL",
721
744
  required=False,
722
745
  )
@@ -990,6 +1013,10 @@ class JobHookForm(BootstrapMixin, forms.ModelForm):
990
1013
  content_types = MultipleContentTypeField(
991
1014
  queryset=ChangeLoggedModelsQuery().as_queryset(), required=True, label="Content Type(s)"
992
1015
  )
1016
+ job = DynamicModelChoiceField(
1017
+ queryset=Job.objects.filter(is_job_hook_receiver=True),
1018
+ query_params={"is_job_hook_receiver": True},
1019
+ )
993
1020
 
994
1021
  class Meta:
995
1022
  model = JobHook
@@ -1157,14 +1184,19 @@ class JobButtonForm(BootstrapMixin, forms.ModelForm):
1157
1184
  api_url="/api/extras/content-types/",
1158
1185
  ),
1159
1186
  )
1187
+ job = DynamicModelChoiceField(
1188
+ queryset=Job.objects.filter(is_job_button_receiver=True),
1189
+ query_params={"is_job_button_receiver": True},
1190
+ )
1160
1191
 
1161
1192
  class Meta:
1162
1193
  model = JobButton
1163
1194
  fields = (
1164
1195
  "content_types",
1165
1196
  "name",
1166
- "text",
1167
1197
  "job",
1198
+ "enabled",
1199
+ "text",
1168
1200
  "weight",
1169
1201
  "group_name",
1170
1202
  "button_class",
@@ -1184,6 +1216,9 @@ class JobButtonBulkEditForm(BootstrapMixin, BulkEditForm):
1184
1216
  ),
1185
1217
  required=False,
1186
1218
  )
1219
+ enabled = forms.NullBooleanField(
1220
+ required=False, widget=BulkEditNullBooleanSelect, help_text="Whether this job button appears in the UI"
1221
+ )
1187
1222
  weight = forms.IntegerField(required=False)
1188
1223
  group_name = forms.CharField(required=False)
1189
1224
 
@@ -1429,6 +1464,17 @@ class RoleBulkEditForm(NautobotBulkEditForm):
1429
1464
  nullable_fields = ["weight"]
1430
1465
 
1431
1466
 
1467
+ class RoleFilterForm(NautobotFilterForm):
1468
+ model = Role
1469
+ q = forms.CharField(required=False, label="Search")
1470
+ content_types = MultipleContentTypeField(
1471
+ queryset=RoleModelsQuery().as_queryset(),
1472
+ required=False,
1473
+ choices_as_strings=True,
1474
+ label="Content Type(s)",
1475
+ )
1476
+
1477
+
1432
1478
  #
1433
1479
  # Secrets
1434
1480
  #
@@ -45,7 +45,6 @@ __all__ = (
45
45
  # 2.0 TODO: remove the below deprecated aliases
46
46
  "AddRemoveTagsForm",
47
47
  "CustomFieldBulkEditForm",
48
- "CustomFieldFilterForm",
49
48
  "CustomFieldModelForm",
50
49
  "RelationshipModelForm",
51
50
  "RoleModelBulkEditFormMixin",
@@ -767,11 +766,6 @@ class CustomFieldBulkEditForm(CustomFieldModelBulkEditFormMixin):
767
766
  pass
768
767
 
769
768
 
770
- @class_deprecated_in_favor_of(CustomFieldModelFilterFormMixin)
771
- class CustomFieldFilterForm(CustomFieldModelFilterFormMixin):
772
- pass
773
-
774
-
775
769
  @class_deprecated_in_favor_of(CustomFieldModelFormMixin)
776
770
  class CustomFieldModelForm(CustomFieldModelFormMixin):
777
771
  pass
nautobot/extras/jobs.py CHANGED
@@ -1168,6 +1168,14 @@ def enqueue_job_hooks(object_change):
1168
1168
  job_hooks = JobHook.objects.filter(content_types=content_type, enabled=True, **{action_flag: True})
1169
1169
 
1170
1170
  # Enqueue the jobs related to the job_hooks
1171
+ get_jobs(reload=True)
1171
1172
  for job_hook in job_hooks:
1172
1173
  job_model = job_hook.job
1173
- JobResult.enqueue_job(job_model, object_change.user, object_change=object_change.pk)
1174
+ if not job_model.installed or not job_model.enabled:
1175
+ logger.warning(
1176
+ "JobHook %s is enabled, but the underlying Job %s is not installed and enabled", job_hook, job_model
1177
+ )
1178
+ elif get_job(job_model.class_path) is None:
1179
+ logger.error("JobHook %s is enabled, but the underlying Job implementation is missing", job_hook)
1180
+ else:
1181
+ JobResult.enqueue_job(job_model, object_change.user, object_change=object_change.pk)
@@ -0,0 +1,28 @@
1
+ # Generated by Django 3.2.25 on 2024-06-17 13:24
2
+
3
+ from django.db import migrations
4
+
5
+ import nautobot.core.models.fields
6
+ import nautobot.core.models.validators
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+ dependencies = [
11
+ ("extras", "0106_populate_default_statuses_and_roles_for_contact_associations"),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AlterField(
16
+ model_name="externalintegration",
17
+ name="remote_url",
18
+ field=nautobot.core.models.fields.LaxURLField(max_length=500),
19
+ ),
20
+ migrations.AlterField(
21
+ model_name="gitrepository",
22
+ name="remote_url",
23
+ field=nautobot.core.models.fields.LaxURLField(
24
+ max_length=255,
25
+ validators=[nautobot.core.models.validators.EnhancedURLValidator(schemes=["http", "https"])],
26
+ ),
27
+ ),
28
+ ]
@@ -0,0 +1,17 @@
1
+ # Generated by Django 3.2.25 on 2024-06-17 19:06
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("extras", "0107_laxurlfield"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.AddField(
13
+ model_name="jobbutton",
14
+ name="enabled",
15
+ field=models.BooleanField(default=True),
16
+ ),
17
+ ]