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
@@ -30,6 +30,7 @@ from nautobot.extras.constants import HTTP_CONTENT_TYPE_JSON
30
30
  from nautobot.extras.filters import (
31
31
  ComputedFieldFilterSet,
32
32
  ConfigContextFilterSet,
33
+ ContactAssociationFilterSet,
33
34
  ContactFilterSet,
34
35
  ContentTypeFilterSet,
35
36
  CustomFieldChoiceFilterSet,
@@ -555,6 +556,74 @@ class ContactFilterSetTestCase(ContactAndTeamFilterSetTestCaseMixin, FilterTestC
555
556
  )
556
557
 
557
558
 
559
+ class ContactAssociationFilterSetTestCase(FilterTestCases.FilterTestCase):
560
+ queryset = ContactAssociation.objects.all()
561
+ filterset = ContactAssociationFilterSet
562
+
563
+ generic_filter_tests = (
564
+ ["status", "status__id"],
565
+ ["status", "status__name"],
566
+ ["contact", "contact__id"],
567
+ ["contact", "contact__name"],
568
+ ["team", "team__id"],
569
+ ["team", "team__name"],
570
+ ["role", "role__id"],
571
+ ["role", "role__name"],
572
+ )
573
+
574
+ @classmethod
575
+ def setUpTestData(cls):
576
+ roles = Role.objects.get_for_model(ContactAssociation)
577
+ statuses = Status.objects.get_for_model(ContactAssociation)
578
+ ip_addresses = IPAddress.objects.all()
579
+ locations = Location.objects.all()
580
+
581
+ cls.location_ct = ContentType.objects.get_for_model(Location)
582
+ ipaddress_ct = ContentType.objects.get_for_model(IPAddress)
583
+
584
+ ContactAssociation.objects.create(
585
+ contact=Contact.objects.first(),
586
+ associated_object_type=ipaddress_ct,
587
+ associated_object_id=ip_addresses[0].pk,
588
+ role=roles[2],
589
+ status=statuses[1],
590
+ )
591
+ ContactAssociation.objects.create(
592
+ contact=Contact.objects.last(),
593
+ associated_object_type=ipaddress_ct,
594
+ associated_object_id=ip_addresses[1].pk,
595
+ role=roles[1],
596
+ status=statuses[2],
597
+ )
598
+ ContactAssociation.objects.create(
599
+ team=Team.objects.first(),
600
+ associated_object_type=cls.location_ct,
601
+ associated_object_id=locations[0].pk,
602
+ role=roles[3],
603
+ status=statuses[0],
604
+ )
605
+ ContactAssociation.objects.create(
606
+ team=Team.objects.last(),
607
+ associated_object_type=cls.location_ct,
608
+ associated_object_id=locations[1].pk,
609
+ role=roles[0],
610
+ status=statuses[1],
611
+ )
612
+
613
+ def test_associated_object_type(self):
614
+ params = {"associated_object_type": "dcim.location"}
615
+ self.assertEqual(
616
+ self.filterset(params, self.queryset).qs.count(),
617
+ ContactAssociation.objects.filter(associated_object_type=self.location_ct).count(),
618
+ )
619
+
620
+ params = {"associated_object_type": self.location_ct.pk}
621
+ self.assertEqual(
622
+ self.filterset(params, self.queryset).qs.count(),
623
+ ContactAssociation.objects.filter(associated_object_type=self.location_ct).count(),
624
+ )
625
+
626
+
558
627
  class CustomFieldChoiceFilterSetTestCase(FilterTestCases.FilterTestCase):
559
628
  queryset = CustomFieldChoice.objects.all()
560
629
  filterset = CustomFieldChoiceFilterSet
@@ -14,7 +14,6 @@ from nautobot.extras.forms import (
14
14
  ConfigContextFilterForm,
15
15
  ConfigContextForm,
16
16
  CustomFieldModelBulkEditFormMixin,
17
- CustomFieldModelFilterFormMixin,
18
17
  CustomFieldModelFormMixin,
19
18
  JobButtonForm,
20
19
  JobEditForm,
@@ -1089,7 +1088,6 @@ class DeprecatedAliasesTestCase(TestCase):
1089
1088
  AddRemoveTagsForm,
1090
1089
  CustomFieldBulkCreateForm,
1091
1090
  CustomFieldBulkEditForm,
1092
- CustomFieldFilterForm,
1093
1091
  CustomFieldModelForm,
1094
1092
  RelationshipModelForm,
1095
1093
  StatusBulkEditFormMixin,
@@ -1100,7 +1098,6 @@ class DeprecatedAliasesTestCase(TestCase):
1100
1098
  (AddRemoveTagsForm, TagsBulkEditFormMixin),
1101
1099
  (CustomFieldBulkEditForm, CustomFieldModelBulkEditFormMixin),
1102
1100
  (CustomFieldBulkCreateForm, CustomFieldModelBulkEditFormMixin),
1103
- (CustomFieldFilterForm, CustomFieldModelFilterFormMixin),
1104
1101
  (CustomFieldModelForm, CustomFieldModelFormMixin),
1105
1102
  (RelationshipModelForm, RelationshipModelFormMixin),
1106
1103
  (StatusBulkEditFormMixin, StatusModelBulkEditFormMixin),
@@ -254,7 +254,7 @@ class ConfigContextTest(ModelTestCases.BaseModelTestCase):
254
254
  slug="test_git_repo",
255
255
  remote_url="http://localhost/git.git",
256
256
  )
257
- repo.save()
257
+ repo.validated_save()
258
258
 
259
259
  with self.assertRaises(ValidationError):
260
260
  nonduplicate_context = ConfigContext(name="context 1", weight=300, data={"a": "22"}, owner=repo)
@@ -864,7 +864,7 @@ class ExportTemplateTest(ModelTestCases.BaseModelTestCase):
864
864
  slug="test_git_repo",
865
865
  remote_url="http://localhost/git.git",
866
866
  )
867
- repo.save()
867
+ repo.validated_save()
868
868
 
869
869
  with self.assertRaises(ValidationError):
870
870
  nonduplicate_template = ExportTemplate(
@@ -888,7 +888,7 @@ class ExternalIntegrationTest(ModelTestCases.BaseModelTestCase):
888
888
  )
889
889
  ei.validated_save()
890
890
 
891
- ei.remote_url = "http://localhost"
891
+ ei.remote_url = "http://some-local-host"
892
892
  ei.validated_save()
893
893
 
894
894
  def test_timeout_validation(self):
@@ -1045,6 +1045,11 @@ class GitRepositoryTest(ModelTestCases.BaseModelTestCase):
1045
1045
  repo.validated_save()
1046
1046
  self.assertIn("Please choose a different slug", str(handler.exception))
1047
1047
 
1048
+ def test_remote_url_hostname(self):
1049
+ """Confirm that a bare hostname (no domain name) can be used for a remote URL."""
1050
+ self.repo.remote_url = "http://some-private-host/example.git"
1051
+ self.repo.validated_save()
1052
+
1048
1053
 
1049
1054
  class JobModelTest(ModelTestCases.BaseModelTestCase):
1050
1055
  """
@@ -17,6 +17,7 @@ from nautobot.core.choices import ColorChoices
17
17
  from nautobot.core.models.fields import slugify_dashes_to_underscores
18
18
  from nautobot.core.testing import extract_form_failures, extract_page_body, TestCase, ViewTestCases
19
19
  from nautobot.core.testing.utils import disable_warnings, post_data
20
+ from nautobot.core.utils.permissions import get_permission_for_model
20
21
  from nautobot.dcim.models import (
21
22
  ConsolePort,
22
23
  Controller,
@@ -777,9 +778,11 @@ class DynamicGroupTestCase(
777
778
  content_type = ContentType.objects.get_for_model(Device)
778
779
 
779
780
  # DynamicGroup objects to test.
780
- DynamicGroup.objects.create(name="DG 1", content_type=content_type)
781
- DynamicGroup.objects.create(name="DG 2", content_type=content_type)
782
- DynamicGroup.objects.create(name="DG 3", content_type=content_type)
781
+ cls.dynamic_groups = [
782
+ DynamicGroup.objects.create(name="DG 1", content_type=content_type),
783
+ DynamicGroup.objects.create(name="DG 2", content_type=content_type),
784
+ DynamicGroup.objects.create(name="DG 3", content_type=content_type),
785
+ ]
783
786
 
784
787
  cls.form_data = {
785
788
  "name": "new_dynamic_group",
@@ -792,6 +795,38 @@ class DynamicGroupTestCase(
792
795
  "dynamic_group_memberships-MAX_NUM_FORMS": "1000",
793
796
  }
794
797
 
798
+ def test_get_object_with_permission(self):
799
+ instance = self._get_queryset().first()
800
+ # Add view permissions for the group's members:
801
+ self.add_permissions(get_permission_for_model(instance.content_type.model_class(), "view"))
802
+
803
+ response = super().test_get_object_with_permission()
804
+
805
+ response_body = extract_page_body(response.content.decode(response.charset))
806
+ # Check that the "members" table in the detail view includes all appropriate member objects
807
+ for member in instance.members:
808
+ self.assertIn(str(member.pk), response_body)
809
+
810
+ def test_get_object_with_constrained_permission(self):
811
+ instance = self._get_queryset().first()
812
+ # Add view permission for one of the group's members but not the others:
813
+ member1, member2 = instance.members[:2]
814
+ obj_perm = ObjectPermission(
815
+ name="Members permission",
816
+ constraints={"pk": member1.pk},
817
+ actions=["view"],
818
+ )
819
+ obj_perm.save()
820
+ obj_perm.users.add(self.user)
821
+ obj_perm.object_types.add(instance.content_type)
822
+
823
+ response = super().test_get_object_with_constrained_permission()
824
+
825
+ response_body = extract_page_body(response.content.decode(response.charset))
826
+ # Check that the "members" table in the detail view includes all permitted member objects
827
+ self.assertIn(str(member1.pk), response_body)
828
+ self.assertNotIn(str(member2.pk), response_body)
829
+
795
830
  def test_get_object_dynamic_groups_anonymous(self):
796
831
  url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.first().pk})
797
832
  self.client.logout()
@@ -815,7 +850,6 @@ class DynamicGroupTestCase(
815
850
  self.assertIn("DG 3", response_body, msg=response_body)
816
851
 
817
852
  def test_get_object_dynamic_groups_with_constrained_permission(self):
818
- self.add_permissions("extras.view_dynamicgroup")
819
853
  obj_perm = ObjectPermission(
820
854
  name="View a device",
821
855
  constraints={"pk": Device.objects.first().pk},
@@ -824,12 +858,22 @@ class DynamicGroupTestCase(
824
858
  obj_perm.save()
825
859
  obj_perm.users.add(self.user)
826
860
  obj_perm.object_types.add(ContentType.objects.get_for_model(Device))
861
+ obj_perm_2 = ObjectPermission(
862
+ name="View a Dynamic Group",
863
+ constraints={"pk": self.dynamic_groups[0].pk},
864
+ actions=["view"],
865
+ )
866
+ obj_perm_2.save()
867
+ obj_perm_2.users.add(self.user)
868
+ obj_perm_2.object_types.add(ContentType.objects.get_for_model(DynamicGroup))
827
869
 
828
870
  url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.first().pk})
829
871
  response = self.client.get(url)
830
872
  self.assertHttpStatus(response, 200)
831
873
  response_body = response.content.decode(response.charset)
832
874
  self.assertIn("DG 1", response_body, msg=response_body)
875
+ self.assertNotIn("DG 2", response_body, msg=response_body)
876
+ self.assertNotIn("DG 3", response_body, msg=response_body)
833
877
 
834
878
  url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.last().pk})
835
879
  response = self.client.get(url)
@@ -952,7 +996,7 @@ class GitRepositoryTestCase(
952
996
  # Create four GitRepository records
953
997
  repos = (
954
998
  GitRepository(name="Repo 1", slug="repo_1", remote_url="https://example.com/repo1.git"),
955
- GitRepository(name="Repo 2", slug="repo_2", remote_url="https://example.com/repo2.git"),
999
+ GitRepository(name="Repo 2", slug="repo_2", remote_url="https://some-local-host/repo2.git"),
956
1000
  GitRepository(name="Repo 3", slug="repo_3", remote_url="https://example.com/repo3.git"),
957
1001
  GitRepository(name="Repo 4", remote_url="https://example.com/repo4.git", secrets_group=secrets_groups[0]),
958
1002
  )
@@ -962,7 +1006,7 @@ class GitRepositoryTestCase(
962
1006
  cls.form_data = {
963
1007
  "name": "A new Git repository",
964
1008
  "slug": "a_new_git_repository",
965
- "remote_url": "http://example.com/a_new_git_repository.git",
1009
+ "remote_url": "http://another-local-host/a_new_git_repository.git",
966
1010
  "branch": "develop",
967
1011
  "_token": "1234567890abcdef1234567890abcdef",
968
1012
  "secrets_group": secrets_groups[1].pk,
@@ -2285,23 +2329,30 @@ class JobButtonTestCase(
2285
2329
 
2286
2330
  @classmethod
2287
2331
  def setUpTestData(cls):
2332
+ jbr_simple = Job.objects.get(job_class_name="TestJobButtonReceiverSimple")
2333
+ jbr_simple.enabled = True
2334
+ jbr_simple.save()
2335
+ jbr_complex = Job.objects.get(job_class_name="TestJobButtonReceiverComplex")
2336
+ jbr_complex.enabled = True
2337
+ jbr_complex.save()
2338
+
2288
2339
  job_buttons = (
2289
2340
  JobButton.objects.create(
2290
2341
  name="JobButton1",
2291
2342
  text="JobButton1",
2292
- job=Job.objects.get(job_class_name="TestJobButtonReceiverSimple"),
2343
+ job=jbr_simple,
2293
2344
  confirmation=True,
2294
2345
  ),
2295
2346
  JobButton.objects.create(
2296
2347
  name="JobButton2",
2297
2348
  text="JobButton2",
2298
- job=Job.objects.get(job_class_name="TestJobButtonReceiverSimple"),
2349
+ job=jbr_simple,
2299
2350
  confirmation=False,
2300
2351
  ),
2301
2352
  JobButton.objects.create(
2302
2353
  name="JobButton3",
2303
2354
  text="JobButton3",
2304
- job=Job.objects.get(job_class_name="TestJobButtonReceiverComplex"),
2355
+ job=jbr_complex,
2305
2356
  confirmation=True,
2306
2357
  weight=50,
2307
2358
  ),
@@ -2315,7 +2366,7 @@ class JobButtonTestCase(
2315
2366
  "content_types": [location_ct.pk],
2316
2367
  "name": "jobbutton-4",
2317
2368
  "text": "jobbutton text 4",
2318
- "job": Job.objects.get(job_class_name="TestJobButtonReceiverComplex").pk,
2369
+ "job": jbr_complex.pk,
2319
2370
  "weight": 100,
2320
2371
  "button_class": "default",
2321
2372
  "confirmation": False,
@@ -2330,6 +2381,9 @@ class JobButtonRenderingTestCase(TestCase):
2330
2381
  def setUp(self):
2331
2382
  super().setUp()
2332
2383
  self.job = Job.objects.get(job_class_name="TestJobButtonReceiverSimple")
2384
+ self.job.enabled = True
2385
+ self.job.save()
2386
+
2333
2387
  self.job_button_1 = JobButton(
2334
2388
  name="JobButton 1",
2335
2389
  text="JobButton {{ obj.name }}",
@@ -2339,10 +2393,14 @@ class JobButtonRenderingTestCase(TestCase):
2339
2393
  self.job_button_1.validated_save()
2340
2394
  self.job_button_1.content_types.add(ContentType.objects.get_for_model(LocationType))
2341
2395
 
2396
+ job_2 = Job.objects.get(job_class_name="TestJobButtonReceiverComplex")
2397
+ job_2.enabled = True
2398
+ job_2.save()
2399
+
2342
2400
  self.job_button_2 = JobButton(
2343
2401
  name="JobButton 2",
2344
2402
  text="Click me!",
2345
- job=Job.objects.get(job_class_name="TestJobButtonReceiverComplex"),
2403
+ job=job_2,
2346
2404
  confirmation=False,
2347
2405
  )
2348
2406
  self.job_button_2.validated_save()
nautobot/extras/views.py CHANGED
@@ -56,8 +56,6 @@ from .datasources import (
56
56
  enqueue_pull_git_repository_and_refresh_data,
57
57
  get_datasource_contents,
58
58
  )
59
- from .filters import RoleFilterSet
60
- from .forms import RoleBulkEditForm, RoleForm
61
59
  from .jobs import get_job
62
60
  from .models import (
63
61
  ComputedField,
@@ -94,7 +92,6 @@ from .models import (
94
92
  Webhook,
95
93
  )
96
94
  from .registry import registry
97
- from .tables import AssociatedContactsTable, RoleTable
98
95
 
99
96
  logger = logging.getLogger(__name__)
100
97
 
@@ -398,7 +395,7 @@ class ContactAssociationUIViewSet(
398
395
  filterset_class = filters.ContactAssociationFilterSet
399
396
  queryset = ContactAssociation.objects.all()
400
397
  serializer_class = serializers.ContactAssociationSerializer
401
- table_class = AssociatedContactsTable
398
+ table_class = tables.AssociatedContactsTable
402
399
  non_filter_params = ("export", "page", "per_page", "sort")
403
400
 
404
401
 
@@ -506,6 +503,7 @@ class CustomFieldListView(generic.ObjectListView):
506
503
  queryset = CustomField.objects.all()
507
504
  table = tables.CustomFieldTable
508
505
  filterset = filters.CustomFieldFilterSet
506
+ filterset_form = forms.CustomFieldFilterForm
509
507
  action_buttons = ("add",)
510
508
 
511
509
 
@@ -706,7 +704,7 @@ class DynamicGroupView(generic.ObjectView):
706
704
 
707
705
  if table_class is not None:
708
706
  # Members table (for display on Members nav tab)
709
- members_table = table_class(instance.members, orderable=False)
707
+ members_table = table_class(instance.members.restrict(request.user, "view"), orderable=False)
710
708
  paginate = {
711
709
  "paginator_class": EnhancedPaginator,
712
710
  "per_page": get_paginate_count(request),
@@ -886,7 +884,9 @@ class ObjectDynamicGroupsView(generic.GenericView):
886
884
  obj = get_object_or_404(model, **kwargs)
887
885
 
888
886
  # Gather all dynamic groups for this object (and its related objects)
889
- dynamicsgroups_table = tables.DynamicGroupTable(data=obj.dynamic_groups_cached, orderable=False)
887
+ dynamicsgroups_table = tables.DynamicGroupTable(
888
+ data=obj.dynamic_groups_cached.restrict(request.user, "view"), orderable=False
889
+ )
890
890
 
891
891
  # Apply the request context
892
892
  paginate = {
@@ -950,6 +950,7 @@ class ExportTemplateBulkDeleteView(generic.BulkDeleteView):
950
950
  class ExternalIntegrationUIViewSet(NautobotUIViewSet):
951
951
  bulk_update_form_class = forms.ExternalIntegrationBulkEditForm
952
952
  filterset_class = filters.ExternalIntegrationFilterSet
953
+ filterset_form_class = forms.ExternalIntegrationFilterForm
953
954
  form_class = forms.ExternalIntegrationForm
954
955
  queryset = ExternalIntegration.objects.select_related("secrets_group")
955
956
  serializer_class = serializers.ExternalIntegrationSerializer
@@ -2048,11 +2049,12 @@ class RoleUIViewSet(viewsets.NautobotUIViewSet):
2048
2049
  """`Roles` UIViewSet."""
2049
2050
 
2050
2051
  queryset = Role.objects.all()
2051
- bulk_update_form_class = RoleBulkEditForm
2052
- filterset_class = RoleFilterSet
2053
- form_class = RoleForm
2052
+ bulk_update_form_class = forms.RoleBulkEditForm
2053
+ filterset_class = filters.RoleFilterSet
2054
+ filterset_form_class = forms.RoleFilterForm
2055
+ form_class = forms.RoleForm
2054
2056
  serializer_class = serializers.RoleSerializer
2055
- table_class = RoleTable
2057
+ table_class = tables.RoleTable
2056
2058
 
2057
2059
  def get_extra_context(self, request, instance):
2058
2060
  context = super().get_extra_context(request, instance)
nautobot/ipam/filters.py CHANGED
@@ -433,11 +433,19 @@ class IPAddressFilterSet(
433
433
  method="_has_interface_assignments",
434
434
  label="Has Interface Assignments",
435
435
  )
436
+ nat_inside = django_filters.ModelMultipleChoiceFilter(
437
+ queryset=IPAddress.objects.all(),
438
+ label="NAT (Inside)",
439
+ )
440
+ has_nat_inside = RelatedMembershipBooleanFilter(
441
+ field_name="nat_inside",
442
+ label="Has NAT Inside",
443
+ )
436
444
  ip_version = django_filters.NumberFilter()
437
445
 
438
446
  class Meta:
439
447
  model = IPAddress
440
- fields = ["id", "dns_name", "type", "tags", "mask_length"]
448
+ fields = ["id", "dns_name", "type", "tags", "mask_length", "nat_inside"]
441
449
 
442
450
  def generate_query__has_interface_assignments(self, value):
443
451
  """Helper method used by DynamicGroups and by _assigned_to_interface method."""
nautobot/ipam/forms.py CHANGED
@@ -94,6 +94,12 @@ class NamespaceBulkEditForm(
94
94
  ]
95
95
 
96
96
 
97
+ class NamespaceFilterForm(LocatableModelFilterFormMixin, NautobotFilterForm):
98
+ model = Namespace
99
+ q = forms.CharField(required=False, label="Search")
100
+ name = forms.CharField(required=False)
101
+
102
+
97
103
  #
98
104
  # VRFs
99
105
  #
@@ -142,6 +148,12 @@ class VRFBulkEditForm(TagsBulkEditFormMixin, NautobotBulkEditForm):
142
148
  namespace = DynamicModelChoiceField(queryset=Namespace.objects.all(), required=False)
143
149
  tenant = DynamicModelChoiceField(queryset=Tenant.objects.all(), required=False)
144
150
  description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
151
+ add_prefixes = DynamicModelMultipleChoiceField(
152
+ queryset=Prefix.objects.all(), required=False, query_params={"namespace": "$namespace"}
153
+ )
154
+ remove_prefixes = DynamicModelMultipleChoiceField(
155
+ queryset=Prefix.objects.all(), required=False, query_params={"namespace": "$namespace"}
156
+ )
145
157
 
146
158
  class Meta:
147
159
  nullable_fields = [
@@ -358,6 +370,12 @@ class PrefixBulkEditForm(
358
370
  remove_locations = DynamicModelMultipleChoiceField(
359
371
  queryset=Location.objects.all(), required=False, query_params={"content_type": Prefix._meta.label_lower}
360
372
  )
373
+ add_vrfs = DynamicModelMultipleChoiceField(
374
+ queryset=VRF.objects.all(), required=False, query_params={"namespace": "$namespace"}
375
+ )
376
+ remove_vrfs = DynamicModelMultipleChoiceField(
377
+ queryset=VRF.objects.all(), required=False, query_params={"namespace": "$namespace"}
378
+ )
361
379
  tenant = DynamicModelChoiceField(queryset=Tenant.objects.all(), required=False)
362
380
  rir = DynamicModelChoiceField(queryset=RIR.objects.all(), required=False, label="RIR")
363
381
  date_allocated = forms.DateTimeField(required=False, widget=DateTimePicker)
@@ -646,6 +664,8 @@ class IPAddressFilterForm(NautobotFilterForm, TenancyFilterForm, StatusModelFilt
646
664
  "role",
647
665
  "tenant_group",
648
666
  "tenant",
667
+ "nat_inside",
668
+ "has_nat_inside",
649
669
  ]
650
670
  q = forms.CharField(required=False, label="Search")
651
671
  parent = forms.CharField(
@@ -682,6 +702,12 @@ class IPAddressFilterForm(NautobotFilterForm, TenancyFilterForm, StatusModelFilt
682
702
  widget=StaticSelect2(),
683
703
  )
684
704
  tags = TagFilterField(model)
705
+ nat_inside = DynamicModelChoiceField(queryset=IPAddress.objects.all(), required=False, label="NAT Inside Address")
706
+ has_nat_inside = forms.NullBooleanField(
707
+ required=False,
708
+ label="Has NAT Inside",
709
+ widget=StaticSelect2(choices=BOOLEAN_WITH_BLANK_CHOICES),
710
+ )
685
711
 
686
712
 
687
713
  #
nautobot/ipam/tables.py CHANGED
@@ -473,7 +473,7 @@ class IPAddressTable(StatusTableMixin, RoleTableMixin, BaseTable):
473
473
 
474
474
 
475
475
  class IPAddressDetailTable(IPAddressTable):
476
- nat_inside = tables.Column(linkify=True, orderable=False, verbose_name="NAT (Inside)")
476
+ nat_inside = tables.Column(linkify=True, verbose_name="NAT (Inside)")
477
477
  tenant = TenantColumn()
478
478
  tags = TagColumn(url_name="ipam:ipaddress_list")
479
479
  assigned = BooleanColumn(accessor="assigned_count")
@@ -462,6 +462,7 @@ class IPAddressTestCase(FilterTestCases.FilterTestCase, FilterTestCases.TenancyF
462
462
  queryset = IPAddress.objects.all()
463
463
  filterset = IPAddressFilterSet
464
464
  tenancy_related_name = "ip_addresses"
465
+ generic_filter_tests = (["nat_inside", "nat_inside__id"],)
465
466
 
466
467
  @classmethod
467
468
  def setUpTestData(cls):
@@ -638,6 +639,20 @@ class IPAddressTestCase(FilterTestCases.FilterTestCase, FilterTestCases.TenancyF
638
639
  status=statuses[0],
639
640
  namespace=cls.namespace,
640
641
  )
642
+ IPAddress.objects.create(
643
+ address="10.1.1.1/32",
644
+ tenant=None,
645
+ status=statuses[0],
646
+ namespace=cls.namespace,
647
+ nat_inside=ip0,
648
+ )
649
+ IPAddress.objects.create(
650
+ address="10.2.2.2/32",
651
+ tenant=None,
652
+ status=statuses[0],
653
+ namespace=cls.namespace,
654
+ nat_inside=ip1,
655
+ )
641
656
 
642
657
  def test_search(self):
643
658
  ipv4_octets = self.ipv4_address.host.split(".")
@@ -71,7 +71,8 @@ class VRFTestCase(ViewTestCases.PrimaryObjectViewTestCase):
71
71
  @classmethod
72
72
  def setUpTestData(cls):
73
73
  tenants = Tenant.objects.all()[:2]
74
- namespace = Namespace.objects.create(name="ipam_test_views_vrf_test")
74
+ namespace = Prefix.objects.first().namespace
75
+ prefixes = Prefix.objects.filter(namespace=namespace)
75
76
 
76
77
  cls.form_data = {
77
78
  "name": "VRF X",
@@ -79,12 +80,16 @@ class VRFTestCase(ViewTestCases.PrimaryObjectViewTestCase):
79
80
  "rd": "65000:999",
80
81
  "tenant": tenants[0].pk,
81
82
  "description": "A new VRF",
83
+ "prefixes": [prefixes[1].id],
82
84
  "tags": [t.pk for t in Tag.objects.get_for_model(VRF)],
83
85
  }
84
86
 
85
87
  cls.bulk_edit_data = {
86
88
  "tenant": tenants[1].pk,
87
89
  "description": "New description",
90
+ "namespace": prefixes[0].namespace.id,
91
+ "add_prefixes": [prefixes[0].id],
92
+ "remove_prefixes": [prefixes[1].id],
88
93
  }
89
94
 
90
95
 
@@ -158,7 +163,6 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase, ViewTestCases.List
158
163
 
159
164
  cls.bulk_edit_data = {
160
165
  "tenant": None,
161
- # TODO "vrf": vrfs[1].pk,
162
166
  "status": cls.statuses[1].pk,
163
167
  "role": cls.roles[1].pk,
164
168
  "rir": RIR.objects.last().pk,
@@ -166,6 +170,9 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase, ViewTestCases.List
166
170
  "description": "New description",
167
171
  "add_locations": [cls.locations[0].pk],
168
172
  "remove_locations": [cls.locations[1].pk],
173
+ "namespace": vrfs[0].namespace.pk,
174
+ "add_vrfs": [vrfs[0].pk],
175
+ "remove_vrfs": [vrfs[1].pk],
169
176
  }
170
177
 
171
178
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
nautobot/ipam/views.py CHANGED
@@ -80,6 +80,7 @@ class NamespaceUIViewSet(
80
80
  form_class = forms.NamespaceForm
81
81
  bulk_update_form_class = forms.NamespaceBulkEditForm
82
82
  filterset_class = filters.NamespaceFilterSet
83
+ filterset_form_class = forms.NamespaceFilterForm
83
84
  queryset = Namespace.objects.all()
84
85
  serializer_class = serializers.NamespaceSerializer
85
86
  table_class = tables.NamespaceTable
@@ -303,6 +304,12 @@ class VRFBulkEditView(generic.BulkEditView):
303
304
  table = tables.VRFTable
304
305
  form = forms.VRFBulkEditForm
305
306
 
307
+ def extra_post_save_action(self, obj, form):
308
+ if form.cleaned_data.get("add_prefixes", None):
309
+ obj.prefixes.add(*form.cleaned_data["add_prefixes"])
310
+ if form.cleaned_data.get("remove_prefixes", None):
311
+ obj.prefixes.remove(*form.cleaned_data["remove_prefixes"])
312
+
306
313
 
307
314
  class VRFBulkDeleteView(generic.BulkDeleteView):
308
315
  queryset = VRF.objects.select_related("tenant")
@@ -713,6 +720,10 @@ class PrefixBulkEditView(generic.BulkEditView):
713
720
  obj.locations.add(*form.cleaned_data["add_locations"])
714
721
  if form.cleaned_data.get("remove_locations", None):
715
722
  obj.locations.remove(*form.cleaned_data["remove_locations"])
723
+ if form.cleaned_data.get("add_vrfs", None):
724
+ obj.vrfs.add(*form.cleaned_data["add_vrfs"])
725
+ if form.cleaned_data.get("remove_vrfs", None):
726
+ obj.vrfs.remove(*form.cleaned_data["remove_vrfs"])
716
727
 
717
728
 
718
729
  class PrefixBulkDeleteView(generic.BulkDeleteView):