nautobot 2.3.7__py3-none-any.whl → 2.3.9__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 (336) hide show
  1. nautobot/apps/tables.py +2 -0
  2. nautobot/core/forms/__init__.py +4 -0
  3. nautobot/core/forms/fields.py +32 -0
  4. nautobot/core/jobs/__init__.py +24 -8
  5. nautobot/core/models/tree_queries.py +8 -0
  6. nautobot/core/settings.py +7 -0
  7. nautobot/core/settings.yaml +10 -0
  8. nautobot/core/signals.py +5 -4
  9. nautobot/core/templates/inc/paginator.html +3 -0
  10. nautobot/core/templates/nautobot_config.py.j2 +4 -0
  11. nautobot/core/templates/utilities/obj_table.html +1 -1
  12. nautobot/dcim/api/serializers.py +10 -5
  13. nautobot/dcim/forms.py +41 -34
  14. nautobot/dcim/models/device_components.py +12 -4
  15. nautobot/dcim/tables/devices.py +4 -2
  16. nautobot/dcim/tests/test_api.py +28 -0
  17. nautobot/dcim/tests/test_forms.py +17 -1
  18. nautobot/dcim/tests/test_models.py +58 -4
  19. nautobot/dcim/utils.py +9 -6
  20. nautobot/extras/context_managers.py +7 -8
  21. nautobot/extras/datasources/__init__.py +2 -0
  22. nautobot/extras/datasources/git.py +30 -49
  23. nautobot/extras/datasources/registry.py +2 -2
  24. nautobot/extras/jobs.py +17 -5
  25. nautobot/extras/models/datasources.py +6 -0
  26. nautobot/extras/models/groups.py +47 -33
  27. nautobot/extras/models/jobs.py +1 -1
  28. nautobot/extras/plugins/__init__.py +165 -0
  29. nautobot/extras/plugins/views.py +18 -3
  30. nautobot/extras/templates/extras/plugin_detail.html +33 -0
  31. nautobot/extras/tests/test_context_managers.py +16 -7
  32. nautobot/extras/tests/test_datasources.py +88 -1
  33. nautobot/extras/tests/test_dynamicgroups.py +12 -0
  34. nautobot/extras/tests/test_plugins.py +94 -0
  35. nautobot/extras/views.py +3 -1
  36. nautobot/ipam/filters.py +2 -2
  37. nautobot/ipam/models.py +29 -2
  38. nautobot/ipam/templates/ipam/ipaddress.html +2 -2
  39. nautobot/ipam/templates/ipam/ipaddress_interfaces.html +3 -0
  40. nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +3 -0
  41. nautobot/ipam/templates/ipam/prefix.html +3 -3
  42. nautobot/ipam/templates/ipam/routetarget.html +2 -2
  43. nautobot/ipam/templates/ipam/vlan.html +3 -0
  44. nautobot/ipam/templates/ipam/vrf.html +7 -4
  45. nautobot/ipam/tests/test_models.py +68 -12
  46. nautobot/ipam/views.py +43 -0
  47. nautobot/project-static/docs/404.html +24 -3
  48. nautobot/project-static/docs/apps/index.html +24 -3
  49. nautobot/project-static/docs/apps/nautobot-apps.html +24 -3
  50. nautobot/project-static/docs/assets/javascripts/{bundle.525ec568.min.js → bundle.83f73b43.min.js} +2 -2
  51. nautobot/project-static/docs/assets/javascripts/{bundle.525ec568.min.js.map → bundle.83f73b43.min.js.map} +3 -3
  52. nautobot/project-static/docs/assets/stylesheets/main.0253249f.min.css +1 -0
  53. nautobot/project-static/docs/assets/stylesheets/main.0253249f.min.css.map +1 -0
  54. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +24 -3
  55. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +24 -3
  56. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +24 -3
  57. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +24 -3
  58. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +24 -3
  59. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +24 -3
  60. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +24 -3
  61. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +24 -3
  62. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +24 -3
  63. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +24 -3
  64. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +24 -3
  65. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +24 -3
  66. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +24 -3
  67. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +49 -6
  68. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +24 -3
  69. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +24 -3
  70. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +24 -3
  71. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +138 -3
  72. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +24 -3
  73. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +24 -3
  74. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +24 -3
  75. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +24 -3
  76. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +24 -3
  77. nautobot/project-static/docs/development/apps/api/configuration-view.html +24 -3
  78. nautobot/project-static/docs/development/apps/api/database-backend-config.html +24 -3
  79. nautobot/project-static/docs/development/apps/api/models/django-admin.html +24 -3
  80. nautobot/project-static/docs/development/apps/api/models/global-search.html +24 -3
  81. nautobot/project-static/docs/development/apps/api/models/graphql.html +24 -3
  82. nautobot/project-static/docs/development/apps/api/models/index.html +24 -3
  83. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +24 -3
  84. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +24 -3
  85. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +24 -3
  86. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +24 -3
  87. nautobot/project-static/docs/development/apps/api/platform-features/index.html +24 -3
  88. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +24 -3
  89. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +24 -3
  90. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +24 -3
  91. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +27 -6
  92. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +8823 -0
  93. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +27 -6
  94. nautobot/project-static/docs/development/apps/api/prometheus.html +24 -3
  95. nautobot/project-static/docs/development/apps/api/setup.html +33 -11
  96. nautobot/project-static/docs/development/apps/api/testing.html +24 -3
  97. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +24 -3
  98. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +24 -3
  99. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +24 -3
  100. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +24 -3
  101. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +24 -3
  102. nautobot/project-static/docs/development/apps/api/views/base-template.html +24 -3
  103. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +24 -3
  104. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +24 -3
  105. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +24 -3
  106. nautobot/project-static/docs/development/apps/api/views/index.html +24 -3
  107. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +24 -3
  108. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +24 -3
  109. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +24 -3
  110. nautobot/project-static/docs/development/apps/api/views/notes.html +24 -3
  111. nautobot/project-static/docs/development/apps/api/views/rest-api.html +24 -3
  112. nautobot/project-static/docs/development/apps/api/views/urls.html +24 -3
  113. nautobot/project-static/docs/development/apps/index.html +24 -3
  114. nautobot/project-static/docs/development/apps/migration/code-updates.html +24 -3
  115. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +24 -3
  116. nautobot/project-static/docs/development/apps/migration/from-v1.html +24 -3
  117. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +24 -3
  118. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +24 -3
  119. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +24 -3
  120. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +24 -3
  121. nautobot/project-static/docs/development/apps/porting-from-netbox.html +24 -3
  122. nautobot/project-static/docs/development/core/application-registry.html +24 -3
  123. nautobot/project-static/docs/development/core/best-practices.html +24 -3
  124. nautobot/project-static/docs/development/core/bootstrap-ui.html +24 -3
  125. nautobot/project-static/docs/development/core/caching.html +24 -3
  126. nautobot/project-static/docs/development/core/controllers.html +24 -3
  127. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +24 -3
  128. nautobot/project-static/docs/development/core/generic-views.html +24 -3
  129. nautobot/project-static/docs/development/core/getting-started.html +24 -3
  130. nautobot/project-static/docs/development/core/homepage.html +24 -3
  131. nautobot/project-static/docs/development/core/index.html +24 -3
  132. nautobot/project-static/docs/development/core/model-checklist.html +24 -3
  133. nautobot/project-static/docs/development/core/model-features.html +24 -3
  134. nautobot/project-static/docs/development/core/natural-keys.html +24 -3
  135. nautobot/project-static/docs/development/core/navigation-menu.html +24 -3
  136. nautobot/project-static/docs/development/core/release-checklist.html +24 -3
  137. nautobot/project-static/docs/development/core/role-internals.html +24 -3
  138. nautobot/project-static/docs/development/core/settings.html +24 -3
  139. nautobot/project-static/docs/development/core/style-guide.html +24 -3
  140. nautobot/project-static/docs/development/core/templates.html +24 -3
  141. nautobot/project-static/docs/development/core/testing.html +24 -3
  142. nautobot/project-static/docs/development/core/user-preferences.html +24 -3
  143. nautobot/project-static/docs/development/index.html +24 -3
  144. nautobot/project-static/docs/development/jobs/index.html +24 -3
  145. nautobot/project-static/docs/development/jobs/migration/from-v1.html +24 -3
  146. nautobot/project-static/docs/index.html +24 -3
  147. nautobot/project-static/docs/objects.inv +0 -0
  148. nautobot/project-static/docs/overview/application_stack.html +24 -3
  149. nautobot/project-static/docs/overview/design_philosophy.html +24 -3
  150. nautobot/project-static/docs/release-notes/index.html +24 -3
  151. nautobot/project-static/docs/release-notes/version-1.0.html +24 -3
  152. nautobot/project-static/docs/release-notes/version-1.1.html +24 -3
  153. nautobot/project-static/docs/release-notes/version-1.2.html +24 -3
  154. nautobot/project-static/docs/release-notes/version-1.3.html +24 -3
  155. nautobot/project-static/docs/release-notes/version-1.4.html +24 -3
  156. nautobot/project-static/docs/release-notes/version-1.5.html +24 -3
  157. nautobot/project-static/docs/release-notes/version-1.6.html +24 -3
  158. nautobot/project-static/docs/release-notes/version-2.0.html +24 -3
  159. nautobot/project-static/docs/release-notes/version-2.1.html +24 -3
  160. nautobot/project-static/docs/release-notes/version-2.2.html +24 -3
  161. nautobot/project-static/docs/release-notes/version-2.3.html +337 -109
  162. nautobot/project-static/docs/requirements.txt +1 -1
  163. nautobot/project-static/docs/search/search_index.json +1 -1
  164. nautobot/project-static/docs/sitemap.xml +273 -269
  165. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  166. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +24 -3
  167. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +24 -3
  168. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +24 -3
  169. nautobot/project-static/docs/user-guide/administration/configuration/index.html +24 -3
  170. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +24 -3
  171. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +29 -4
  172. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +24 -3
  173. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +24 -3
  174. nautobot/project-static/docs/user-guide/administration/guides/docker.html +24 -3
  175. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +24 -3
  176. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +24 -3
  177. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +24 -3
  178. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +24 -3
  179. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +24 -3
  180. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +24 -3
  181. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +24 -3
  182. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +24 -3
  183. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +24 -3
  184. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +24 -3
  185. nautobot/project-static/docs/user-guide/administration/installation/index.html +24 -3
  186. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +24 -3
  187. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +24 -3
  188. nautobot/project-static/docs/user-guide/administration/installation/services.html +24 -3
  189. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +24 -3
  190. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +24 -3
  191. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +24 -3
  192. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +24 -3
  193. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +24 -3
  194. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +24 -3
  195. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +24 -3
  196. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +24 -3
  197. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +24 -3
  198. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +24 -3
  199. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +24 -3
  200. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +24 -3
  201. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +24 -3
  202. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +24 -3
  203. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +24 -3
  204. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +24 -3
  205. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +24 -3
  206. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +24 -3
  207. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +24 -3
  208. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +24 -3
  209. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +24 -3
  210. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +24 -3
  211. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +24 -3
  212. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +24 -3
  213. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +24 -3
  214. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +24 -3
  215. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +24 -3
  216. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +24 -3
  217. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +24 -3
  218. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +24 -3
  219. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +24 -3
  220. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +24 -3
  221. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +24 -3
  222. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +24 -3
  223. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +24 -3
  224. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +24 -3
  225. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +24 -3
  226. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +24 -3
  227. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +24 -3
  228. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +24 -3
  229. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +24 -3
  230. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +24 -3
  231. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +24 -3
  232. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +24 -3
  233. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +24 -3
  234. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +24 -3
  235. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +24 -3
  236. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +24 -3
  237. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +24 -3
  238. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +24 -3
  239. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +24 -3
  240. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +24 -3
  241. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +24 -3
  242. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +24 -3
  243. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +24 -3
  244. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +24 -3
  245. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +24 -3
  246. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +24 -3
  247. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +24 -3
  248. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +24 -3
  249. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +24 -3
  250. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +24 -3
  251. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +24 -3
  252. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +24 -3
  253. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +24 -3
  254. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +24 -3
  255. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +24 -3
  256. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +24 -3
  257. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +24 -3
  258. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +24 -3
  259. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +24 -3
  260. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +24 -3
  261. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +24 -3
  262. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +24 -3
  263. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +24 -3
  264. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +24 -3
  265. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +24 -3
  266. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +24 -3
  267. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +24 -3
  268. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +24 -3
  269. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +24 -3
  270. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +24 -3
  271. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +24 -3
  272. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +24 -3
  273. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +24 -3
  274. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +24 -3
  275. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +24 -3
  276. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +24 -3
  277. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +24 -3
  278. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +24 -3
  279. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +24 -3
  280. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +24 -3
  281. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +24 -3
  282. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +24 -3
  283. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +24 -3
  284. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +24 -3
  285. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +24 -3
  286. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +24 -3
  287. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +24 -3
  288. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +24 -3
  289. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +24 -3
  290. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +24 -3
  291. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +24 -3
  292. nautobot/project-static/docs/user-guide/index.html +24 -3
  293. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +24 -3
  294. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +24 -3
  295. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +24 -3
  296. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +24 -3
  297. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +24 -3
  298. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +24 -3
  299. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +24 -3
  300. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +24 -3
  301. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +24 -3
  302. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +24 -3
  303. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +24 -3
  304. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +24 -3
  305. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +24 -3
  306. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +24 -3
  307. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +24 -3
  308. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +24 -3
  309. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +24 -3
  310. nautobot/project-static/docs/user-guide/platform-functionality/note.html +24 -3
  311. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +24 -3
  312. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +24 -3
  313. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +24 -3
  314. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +24 -3
  315. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +24 -3
  316. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +24 -3
  317. nautobot/project-static/docs/user-guide/platform-functionality/role.html +24 -3
  318. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +24 -3
  319. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +24 -3
  320. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +24 -3
  321. nautobot/project-static/docs/user-guide/platform-functionality/status.html +24 -3
  322. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +24 -3
  323. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +24 -3
  324. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +24 -3
  325. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +24 -3
  326. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +24 -3
  327. nautobot/project-static/js/forms.js +41 -5
  328. nautobot/virtualization/tables.py +1 -1
  329. {nautobot-2.3.7.dist-info → nautobot-2.3.9.dist-info}/METADATA +2 -2
  330. {nautobot-2.3.7.dist-info → nautobot-2.3.9.dist-info}/RECORD +334 -333
  331. nautobot/project-static/docs/assets/stylesheets/main.8c3ca2c6.min.css +0 -1
  332. nautobot/project-static/docs/assets/stylesheets/main.8c3ca2c6.min.css.map +0 -1
  333. {nautobot-2.3.7.dist-info → nautobot-2.3.9.dist-info}/LICENSE.txt +0 -0
  334. {nautobot-2.3.7.dist-info → nautobot-2.3.9.dist-info}/NOTICE +0 -0
  335. {nautobot-2.3.7.dist-info → nautobot-2.3.9.dist-info}/WHEEL +0 -0
  336. {nautobot-2.3.7.dist-info → nautobot-2.3.9.dist-info}/entry_points.txt +0 -0
@@ -30,6 +30,21 @@ class InstalledAppsView(GenericView):
30
30
  data = []
31
31
  for app in apps.get_app_configs():
32
32
  if app.name in settings.PLUGINS:
33
+ try:
34
+ reverse(app.home_view_name)
35
+ home_url = app.home_view_name
36
+ except NoReverseMatch:
37
+ home_url = None
38
+ try:
39
+ reverse(app.config_view_name)
40
+ config_url = app.config_view_name
41
+ except NoReverseMatch:
42
+ config_url = None
43
+ try:
44
+ reverse(app.docs_view_name)
45
+ docs_url = app.docs_view_name
46
+ except NoReverseMatch:
47
+ docs_url = None
33
48
  data.append(
34
49
  {
35
50
  "name": app.verbose_name,
@@ -40,9 +55,9 @@ class InstalledAppsView(GenericView):
40
55
  "description": app.description,
41
56
  "version": app.version,
42
57
  "actions": {
43
- "home": app.home_view_name,
44
- "configure": app.config_view_name,
45
- "docs": app.docs_view_name,
58
+ "home": home_url,
59
+ "configure": config_url,
60
+ "docs": docs_url,
46
61
  },
47
62
  }
48
63
  )
@@ -277,6 +277,39 @@
277
277
  {% endif %}
278
278
  </td>
279
279
  </tr>
280
+ <tr>
281
+ <td>Table Extensions</td>
282
+ <td>
283
+ {% if features.table_extensions %}
284
+ {% if features.table_extensions.columns %}
285
+ <b>Custom Columns</b>
286
+ <ul class="list-unstyled">
287
+ {% for column in features.table_extensions.columns %}
288
+ <li><code>{{ column }}</code></li>
289
+ {% endfor %}
290
+ </ul>
291
+ {% endif %}
292
+ {% if features.table_extensions.add_to_default_columns %}
293
+ <b>Additional Default Columns</b>
294
+ <ul class="list-unstyled">
295
+ {% for column in features.table_extensions.add_to_default_columns %}
296
+ <li><code>{{ column }}</code></li>
297
+ {% endfor %}
298
+ </ul>
299
+ {% endif %}
300
+ {% if features.table_extensions.remove_from_default_columns %}
301
+ <b>Remove from Default Columns</b>
302
+ <ul class="list-unstyled">
303
+ {% for column in features.table_extensions.remove_from_default_columns %}
304
+ <li><code>{{ column }}</code></li>
305
+ {% endfor %}
306
+ </ul>
307
+ {% endif %}
308
+ {% else %}
309
+ {% include 'utilities/render_boolean.html' with value=features.table_extensions %}
310
+ {% endif %}
311
+ </td>
312
+ </tr>
280
313
  <tr>
281
314
  <td>Views/URLs</td>
282
315
  <td>
@@ -5,7 +5,7 @@ from django.contrib.contenttypes.models import ContentType
5
5
  from django.test import TestCase
6
6
 
7
7
  from nautobot.core.celery import app
8
- from nautobot.core.testing import TransactionTestCase
8
+ from nautobot.core.testing import get_job_class_and_model, TransactionTestCase
9
9
  from nautobot.core.utils.lookup import get_changes_for_model
10
10
  from nautobot.dcim.models import (
11
11
  DeviceType,
@@ -22,7 +22,7 @@ from nautobot.extras.context_managers import (
22
22
  deferred_change_logging_for_bulk_operation,
23
23
  web_request_context,
24
24
  )
25
- from nautobot.extras.models import Status, Webhook
25
+ from nautobot.extras.models import JobHook, Status, Webhook
26
26
  from nautobot.extras.utils import bulk_delete_with_bulk_change_logging
27
27
 
28
28
  # Use the proper swappable User model
@@ -74,7 +74,7 @@ class WebRequestContextTestCase(TestCase):
74
74
  self.assertEqual(oc_list[0].changed_object, location)
75
75
  self.assertEqual(oc_list[0].action, ObjectChangeActionChoices.ACTION_CREATE)
76
76
 
77
- @mock.patch("nautobot.extras.jobs.enqueue_job_hooks")
77
+ @mock.patch("nautobot.extras.jobs.enqueue_job_hooks", return_value=True)
78
78
  @mock.patch("nautobot.extras.context_managers.enqueue_webhooks")
79
79
  def test_create_then_delete(self, mock_enqueue_webhooks, mock_enqueue_job_hooks):
80
80
  """Test that a create followed by a delete is logged as two changes"""
@@ -88,11 +88,13 @@ class WebRequestContextTestCase(TestCase):
88
88
 
89
89
  location = Location.objects.filter(pk=location_pk)
90
90
  self.assertFalse(location.exists())
91
- oc_list = get_changes_for_model(Location).filter(changed_object_id=location_pk)
91
+ oc_list = get_changes_for_model(Location).filter(changed_object_id=location_pk).order_by("time")
92
92
  self.assertEqual(len(oc_list), 2)
93
- self.assertEqual(oc_list[0].action, ObjectChangeActionChoices.ACTION_DELETE)
94
- self.assertEqual(oc_list[1].action, ObjectChangeActionChoices.ACTION_CREATE)
95
- mock_enqueue_job_hooks.assert_has_calls([mock.call(oc_list[0]), mock.call(oc_list[1])])
93
+ self.assertEqual(oc_list[0].action, ObjectChangeActionChoices.ACTION_CREATE)
94
+ self.assertEqual(oc_list[1].action, ObjectChangeActionChoices.ACTION_DELETE)
95
+ mock_enqueue_job_hooks.assert_has_calls(
96
+ [mock.call(oc_list[0], may_reload_jobs=True), mock.call(oc_list[1], may_reload_jobs=False)],
97
+ )
96
98
  mock_enqueue_webhooks.assert_has_calls([mock.call(oc_list[0]), mock.call(oc_list[1])])
97
99
 
98
100
  def test_update_then_delete(self):
@@ -307,6 +309,13 @@ class BulkEditDeleteChangeLogging(TestCase):
307
309
  for i in range(1, 4)
308
310
  ]
309
311
  Location.objects.bulk_create(locations)
312
+ # Create a JobHook that applies to Locations
313
+ _, job_model = get_job_class_and_model("job_hook_receiver", "TestJobHookReceiverLog")
314
+ mock_import_jobs.assert_called_once()
315
+ mock_import_jobs.reset_mock()
316
+ job_hook = JobHook.objects.create(name="JobHookTest", type_update=True, job=job_model)
317
+ job_hook.content_types.set([ContentType.objects.get_for_model(Location)])
318
+
310
319
  pk_list = []
311
320
  with web_request_context(self.user):
312
321
  with deferred_change_logging_for_bulk_operation():
@@ -412,7 +412,7 @@ class GitTest(TransactionTestCase):
412
412
 
413
413
  def test_pull_git_repository_and_refresh_data_with_bad_data(self):
414
414
  """
415
- The test_pull_git_repository_and_refresh_data job should gracefully handle bad data in the Git repository
415
+ The test_pull_git_repository_and_refresh_data job should gracefully handle bad data in the Git repository.
416
416
  """
417
417
  with tempfile.TemporaryDirectory() as tempdir:
418
418
  with self.settings(GIT_ROOT=tempdir):
@@ -433,6 +433,18 @@ class GitTest(TransactionTestCase):
433
433
  job_result.result,
434
434
  )
435
435
 
436
+ # Due to transaction rollback on failure, the database should still/again match the pre-sync state, of
437
+ # no records owned by the repository.
438
+ self.assertFalse(ConfigContextSchema.objects.filter(owner_object_id=self.repo.id).exists())
439
+ self.assertFalse(ConfigContext.objects.filter(owner_object_id=self.repo.id).exists())
440
+ self.assertFalse(ExportTemplate.objects.filter(owner_object_id=self.repo.id).exists())
441
+ self.assertFalse(Job.objects.filter(module_name__startswith=f"{self.repo.slug}.").exists())
442
+ device = Device.objects.get(name=self.device.name)
443
+ self.assertIsNone(device.local_config_context_data)
444
+ self.assertIsNone(device.local_config_context_data_owner)
445
+ self.repo.refresh_from_db()
446
+ self.assertEqual(self.repo.current_head, "")
447
+
436
448
  # Check for specific log messages
437
449
  log_entries = JobLogEntry.objects.filter(job_result=job_result)
438
450
  warning_logs = log_entries.filter(log_level=LogLevelChoices.LOG_WARNING)
@@ -592,6 +604,81 @@ class GitTest(TransactionTestCase):
592
604
 
593
605
  self.assert_job_exists(installed=False)
594
606
 
607
+ def test_git_repository_sync_rollback(self):
608
+ """
609
+ Once a "known-good" sync state is achieved, resync to a new "bad" head commit should fail and be rolled back.
610
+ """
611
+ with tempfile.TemporaryDirectory() as tempdir:
612
+ with self.settings(GIT_ROOT=tempdir):
613
+ # Initially have a successful sync to a good commit that provides data
614
+ self.repo.branch = "valid-files" # actually a tag
615
+ self.repo.save()
616
+ job_model = GitRepositorySync().job_model
617
+ job_result = run_job_for_testing(job=job_model, repository=self.repo.pk)
618
+ job_result.refresh_from_db()
619
+ self.assertEqual(
620
+ job_result.status,
621
+ JobResultStatusChoices.STATUS_SUCCESS,
622
+ (job_result.traceback, list(job_result.job_log_entries.values_list("message", flat=True))),
623
+ )
624
+
625
+ self.assert_explicit_config_context_exists("Frobozz 1000 NTP servers")
626
+ self.assert_implicit_config_context_exists("Location context")
627
+ self.assert_config_context_schema_record_exists("Config Context Schema 1")
628
+ self.assert_device_exists(self.device.name)
629
+ self.assert_export_template_device("template.j2")
630
+ self.assert_export_template_html_exist("template2.html")
631
+ self.assert_export_template_vlan_exists("template.j2")
632
+ self.assert_job_exists(name="MyJob")
633
+ self.assert_job_exists(name="MyJobButtonReceiver")
634
+ self.assert_job_exists(name="MyJobHookReceiver")
635
+
636
+ # Create JobButton and JobHook
637
+ JobButton.objects.create(
638
+ name="MyJobButton", enabled=True, text="Click me", job=Job.objects.get(name="MyJobButtonReceiver")
639
+ )
640
+ JobHook.objects.create(name="MyJobHook", enabled=True, job=Job.objects.get(name="MyJobHookReceiver"))
641
+
642
+ self.repo.refresh_from_db()
643
+ self.assertNotEqual(self.repo.current_head, "")
644
+ good_current_head = self.repo.current_head
645
+
646
+ # Now change to the `main` branch (which includes the current commit, followed by a "bad" commit)
647
+ self.repo.branch = "main"
648
+ self.repo.save()
649
+
650
+ # Resync, attempting and failing to update to the new commit
651
+ job_result = run_job_for_testing(job=job_model, repository=self.repo.pk)
652
+ job_result.refresh_from_db()
653
+ self.assertEqual(
654
+ job_result.status,
655
+ JobResultStatusChoices.STATUS_FAILURE,
656
+ job_result.result,
657
+ )
658
+ log_entries = JobLogEntry.objects.filter(job_result=job_result)
659
+
660
+ # Assert database changes were rolled back
661
+ self.repo.refresh_from_db()
662
+ try:
663
+ self.assertEqual(self.repo.current_head, good_current_head)
664
+ self.assert_explicit_config_context_exists("Frobozz 1000 NTP servers")
665
+ self.assert_implicit_config_context_exists("Location context")
666
+ self.assert_config_context_schema_record_exists("Config Context Schema 1")
667
+ self.assert_device_exists(self.device.name)
668
+ self.assert_export_template_device("template.j2")
669
+ self.assert_export_template_html_exist("template2.html")
670
+ self.assert_export_template_vlan_exists("template.j2")
671
+ self.assert_job_exists(name="MyJob")
672
+ self.assert_job_exists(name="MyJobButtonReceiver")
673
+ self.assert_job_exists(name="MyJobHookReceiver")
674
+ self.assertTrue(JobButton.objects.get(name="MyJobButton").enabled)
675
+ self.assertTrue(JobHook.objects.get(name="MyJobHook").enabled)
676
+ except Exception:
677
+ for log in log_entries:
678
+ print(log.message)
679
+ print(job_result.traceback)
680
+ raise
681
+
595
682
  def test_git_dry_run(self):
596
683
  with tempfile.TemporaryDirectory() as tempdir:
597
684
  with self.settings(GIT_ROOT=tempdir):
@@ -304,6 +304,10 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
304
304
  sg.add_members(Prefix.objects.filter(ip_version=4))
305
305
  self.assertIsInstance(sg.members, PrefixQuerySet)
306
306
  self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=4))
307
+ # test cumulative construction and alternate code path
308
+ sg.add_members(list(Prefix.objects.filter(ip_version=6)))
309
+ self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.all())
310
+ self.assertEqual(sg.static_group_associations.count(), Prefix.objects.all().count())
307
311
  # test duplicate objects aren't re-added
308
312
  sg.add_members(Prefix.objects.all())
309
313
  self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.all())
@@ -321,10 +325,18 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
321
325
  sg.remove_members(list(Prefix.objects.filter(ip_version=4)))
322
326
  self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=6))
323
327
  self.assertEqual(sg.static_group_associations.count(), Prefix.objects.filter(ip_version=6).count())
328
+ # test cumulative removal and alternate code path
329
+ sg.remove_members(list(Prefix.objects.filter(ip_version=6)))
330
+ self.assertQuerysetEqual(sg.members, Prefix.objects.none())
331
+ self.assertEqual(sg.static_group_associations.count(), 0)
324
332
 
325
333
  # test property setter
326
334
  sg.members = Prefix.objects.filter(ip_version=4)
327
335
  self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=4))
336
+ sg.members = Prefix.objects.filter(ip_version=6)
337
+ self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=6))
338
+ sg.members = list(Prefix.objects.filter(ip_version=4))
339
+ self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=4))
328
340
  sg.members = list(Prefix.objects.filter(ip_version=6))
329
341
  self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=6))
330
342
 
@@ -6,12 +6,15 @@ from django.core.exceptions import ValidationError
6
6
  from django.template import engines
7
7
  from django.test import override_settings
8
8
  from django.urls import NoReverseMatch, reverse
9
+ import django_tables2 as tables
9
10
  import netaddr
10
11
 
11
12
  from nautobot.circuits.models import Circuit, CircuitType, Provider
12
13
  from nautobot.core.testing import APIViewTestCases, disable_warnings, extract_page_body, TestCase, ViewTestCases
14
+ from nautobot.core.utils.lookup import get_table_for_model
13
15
  from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Manufacturer
14
16
  from nautobot.dcim.tests.test_views import create_test_device
17
+ from nautobot.extras import plugins
15
18
  from nautobot.extras.choices import CustomFieldTypeChoices, RelationshipTypeChoices
16
19
  from nautobot.extras.jobs import get_job
17
20
  from nautobot.extras.models import CustomField, Relationship, RelationshipAssociation, Role, Secret, Status
@@ -647,6 +650,97 @@ class FilterExtensionTest(TestCase):
647
650
  self.assertIn("example_app_description", form.fields.keys())
648
651
 
649
652
 
653
+ class TableExtensionTest(TestCase):
654
+ """Tests for adding table extensions."""
655
+
656
+ def test_alter_queryset(self):
657
+ """Test the 'alter_queryset' method of the TableExtension class."""
658
+ extension = plugins.TableExtension()
659
+ queryset = object()
660
+ result = extension.alter_queryset(queryset)
661
+ self.assertEqual(result, queryset)
662
+
663
+ def test__add_columns_into_model_table(self):
664
+ """Test the '_add_columns_into_model_table' function."""
665
+ extension = plugins.TableExtension()
666
+ extension.model = "tenancy.tenant"
667
+ extension.table_columns = {
668
+ "tenant_group": tables.Column(verbose_name="Name conflicts with existing column"),
669
+ "tenant_success": tables.Column(verbose_name="This name should be registered"),
670
+ }
671
+ extension.add_to_default_columns = ["tenant_group", "tenant_success"]
672
+
673
+ with self.assertLogs("nautobot.extras.plugins", level="WARNING") as logger:
674
+ plugins._add_columns_into_model_table(extension, "tenant")
675
+ table = get_table_for_model(extension.model)
676
+ expected = [
677
+ "ERROR:nautobot.extras.plugins:tenant: There was a name conflict with existing "
678
+ "table column `tenant_group`, the custom column was ignored."
679
+ ]
680
+ with self.subTest("error is logged"):
681
+ self.assertEqual(logger.output, expected)
682
+
683
+ with self.subTest("column is added to default_columns"):
684
+ self.assertIn("tenant_success", table.base_columns)
685
+
686
+ def test__add_column_to_table_base_columns(self):
687
+ """Test the '_add_column_to_table_base_columns' function."""
688
+ table = get_table_for_model("tenancy.tenant")
689
+
690
+ with self.subTest("raises TypeError"):
691
+ column = object()
692
+ with self.assertRaises(TypeError) as context:
693
+ plugins._add_column_to_table_base_columns(table, "test_column", column, "my_app")
694
+ self.assertEqual(
695
+ context.exception, "Custom column `test_column` is not an instance of django_tables2.Column."
696
+ )
697
+
698
+ with self.subTest("raises AttributeError"):
699
+ column = tables.Column()
700
+ with self.assertRaises(AttributeError) as context:
701
+ plugins._add_column_to_table_base_columns(table, "pk", column, "my_app")
702
+ self.assertEqual(
703
+ context.exception, "There was a conflict with table column `pk`, the custom column was ignored."
704
+ )
705
+
706
+ with self.subTest("Adds column to base_columns"):
707
+ column = tables.Column()
708
+ plugins._add_column_to_table_base_columns(table, "unique_name", column, "my_app")
709
+ self.assertIn("unique_name", table.base_columns)
710
+
711
+ def test__modify_default_table_columns(self):
712
+ """Test the '_modify_default_table_columns' function."""
713
+ extension = plugins.TableExtension()
714
+ extension.model = "tenancy.tenant"
715
+ extension.add_to_default_columns = ["new_column"]
716
+ extension.remove_from_default_columns = ["description"]
717
+
718
+ table = get_table_for_model(extension.model)
719
+ table.base_columns["new_column"] = tables.Column()
720
+
721
+ plugins._modify_default_table_columns(extension, "my_app")
722
+
723
+ with self.subTest("column is added to default_columns"):
724
+ self.assertIn("new_column", table.Meta.default_columns)
725
+
726
+ with self.subTest("column is removed from default_columns"):
727
+ self.assertNotIn("description", table.Meta.default_columns)
728
+
729
+ def test__validate_is_subclass_of_table_extension(self):
730
+ """Test the '_validate_is_subclass_of_table_extension' function."""
731
+ with self.assertRaises(TypeError):
732
+ plugins._validate_is_subclass_of_table_extension(object)
733
+
734
+ def test__validate_table_column_name_is_prefixed_with_app_name(self):
735
+ """Test the '_validate_table_column_name_is_prefixed_with_app_name' function."""
736
+ with self.assertRaises(ValueError) as context:
737
+ plugins._validate_table_column_name_is_prefixed_with_app_name("not_prefixed", "prefix")
738
+ self.assertEqual(
739
+ context.exception,
740
+ "Attempted to create a custom table column `not_prefixed` that did not start with `prefix`",
741
+ )
742
+
743
+
650
744
  class LoadPluginTest(TestCase):
651
745
  """
652
746
  Validate that plugin helpers work as intended.
nautobot/extras/views.py CHANGED
@@ -737,7 +737,9 @@ class DynamicGroupView(generic.ObjectView):
737
737
 
738
738
  if table_class is not None:
739
739
  # Members table (for display on Members nav tab)
740
- members_table = table_class(members.restrict(request.user, "view"), orderable=False)
740
+ members_table = table_class(
741
+ members.restrict(request.user, "view"), orderable=False, exclude=["dynamic_group_count"]
742
+ )
741
743
  paginate = {
742
744
  "paginator_class": EnhancedPaginator,
743
745
  "per_page": get_paginate_count(request),
nautobot/ipam/filters.py CHANGED
@@ -586,13 +586,13 @@ class VLANFilterSet(
586
586
  fields = ["id", "name", "tags", "vid"]
587
587
 
588
588
  def get_for_device(self, queryset, name, value):
589
- # TODO: after Location model replaced Site, which was not a hierarchical model, should we consider to include
590
- # VLANs that belong to the parent/child locations of the `device.location`?
591
589
  """Return all VLANs available to the specified Device(value)."""
592
590
  devices = Device.objects.select_related("location").filter(**{f"{name}__in": value})
593
591
  if not devices.exists():
594
592
  return queryset.none()
595
593
  location_ids = list(devices.values_list("location__id", flat=True))
594
+ for location in Location.objects.filter(pk__in=location_ids):
595
+ location_ids.extend([ancestor.id for ancestor in location.ancestors()])
596
596
  return queryset.filter(Q(locations__isnull=True) | Q(locations__in=location_ids))
597
597
 
598
598
 
nautobot/ipam/models.py CHANGED
@@ -890,12 +890,39 @@ class Prefix(PrimaryModel):
890
890
  )
891
891
  return available_ips
892
892
 
893
+ def get_child_ips(self):
894
+ """
895
+ Return IP addresses with this prefix as their *direct* parent.
896
+
897
+ Does *not* include IPs that descend from a descendant prefix; if those are desired, use get_all_ips() instead.
898
+
899
+ ```
900
+ Prefix 10.0.0.0/16
901
+ IPAddress 10.0.0.1/24
902
+ Prefix 10.0.1.0/24
903
+ IPAddress 10.0.1.1/24
904
+ ```
905
+
906
+ In the above example, `<Prefix 10.0.0.0/16>.get_child_ips()` will *only* return 10.0.0.1/24,
907
+ while `<Prefix 10.0.0.0/16>.get_all_ips()` will return *both* 10.0.0.1.24 and 10.0.1.1/24.
908
+ """
909
+ return self.ip_addresses.all()
910
+
893
911
  def get_all_ips(self):
894
912
  """
895
913
  Return all IP addresses contained within this prefix, including child prefixes' IP addresses.
896
914
 
897
- Returns:
898
- IPAddress QuerySet
915
+ This is distinct from the behavior of `get_child_ips()` and in *most* cases is probably preferred.
916
+
917
+ ```
918
+ Prefix 10.0.0.0/16
919
+ IPAddress 10.0.0.1/24
920
+ Prefix 10.0.1.0/24
921
+ IPAddress 10.0.1.1/24
922
+ ```
923
+
924
+ In the above example, `<Prefix 10.0.0.0/16>.get_child_ips()` will *only* return 10.0.0.1/24,
925
+ while `<Prefix 10.0.0.0/16>.get_all_ips()` will return *both* 10.0.0.1.24 and 10.0.1.1/24.
899
926
  """
900
927
  return IPAddress.objects.filter(
901
928
  parent__namespace=self.namespace, host__gte=self.network, host__lte=self.broadcast
@@ -150,9 +150,9 @@
150
150
  </tr>
151
151
  </table>
152
152
  </div>
153
- {% include 'panel_table.html' with table=parent_prefixes_table heading='Parent Prefixes' %}
154
153
  {% endblock content_right_page %}
155
154
 
156
155
  {% block content_full_width_page %}
157
- {% include 'utilities/obj_table.html' with table=related_ips_table table_template='panel_table.html' heading='Related IP Addresses' panel_class='default' %}
156
+ {% include 'utilities/obj_table.html' with table=parent_prefixes_table table_template='panel_table.html' heading='Parent Prefixes' %}
157
+ {% include 'utilities/obj_table.html' with table=related_ips_table table_template='panel_table.html' heading='Related IP Addresses' %}
158
158
  {% endblock content_full_width_page %}
@@ -21,6 +21,9 @@
21
21
  {% render_table interface_table 'inc/table.html' %}
22
22
  </div>
23
23
  </form>
24
+ {% if interface_table.paginator.num_pages > 1 %}
25
+ {% include "inc/paginator.html" with paginator=interface_table.paginator page=interface_table.page %}
26
+ {% endif %}
24
27
  {% table_config_form interface_table %}
25
28
  {% endblock content %}
26
29
 
@@ -21,6 +21,9 @@
21
21
  {% render_table vm_interface_table 'inc/table.html' %}
22
22
  </div>
23
23
  </form>
24
+ {% if vm_interface_table.paginator.num_pages > 1 %}
25
+ {% include "inc/paginator.html" with paginator=vm_interface_table.paginator page=vm_interface_table.page %}
26
+ {% endif %}
24
27
  {% table_config_form vm_interface_table %}
25
28
  {% endblock content %}
26
29
 
@@ -117,7 +117,7 @@
117
117
  {% endblock content_left_page %}
118
118
 
119
119
  {% block content_right_page %}
120
- {% include 'panel_table.html' with table=parent_prefix_table heading='Parent Prefixes' panel_class='default' %}
121
- {% include "utilities/obj_table.html" with table=vrf_table table_template="panel_table.html" heading="Assigned VRFs" disable_pagination=False %}
122
- {% include "utilities/obj_table.html" with table=cloud_network_table table_template="panel_table.html" heading="Assigned Cloud Networks" disable_pagination=False %}
120
+ {% include "utilities/obj_table.html" with table=parent_prefix_table table_template="panel_table.html" heading="Parent Prefixes" %}
121
+ {% include "utilities/obj_table.html" with table=vrf_table table_template="panel_table.html" heading="Assigned VRFs" %}
122
+ {% include "utilities/obj_table.html" with table=cloud_network_table table_template="panel_table.html" heading="Assigned Cloud Networks" %}
123
123
  {% endblock content_right_page %}
@@ -24,6 +24,6 @@
24
24
  {% endblock content_left_page %}
25
25
 
26
26
  {% block content_right_page %}
27
- {% include 'panel_table.html' with table=importing_vrfs_table heading="Importing VRFs" %}
28
- {% include 'panel_table.html' with table=exporting_vrfs_table heading="Exporting VRFs" %}
27
+ {% include 'utilities/obj_table.html' with table=importing_vrfs_table table_template='panel_table.html' heading="Importing VRFs" %}
28
+ {% include 'utilities/obj_table.html' with table=exporting_vrfs_table table_template='panel_table.html' heading="Exporting VRFs" %}
29
29
  {% endblock content_right_page %}
@@ -86,4 +86,7 @@
86
86
  </div>
87
87
  {% endif %}
88
88
  </div>
89
+ {% if prefix_table.paginator.num_pages > 1 %}
90
+ {% include "inc/paginator.html" with paginator=prefix_table.paginator page=prefix_table.page %}
91
+ {% endif %}
89
92
  {% endblock content_full_width_page %}
@@ -38,8 +38,11 @@
38
38
  {% endblock content_left_page %}
39
39
 
40
40
  {% block content_right_page %}
41
- {% include 'panel_table.html' with table=import_targets_table heading="Import Route Targets" %}
42
- {% include 'panel_table.html' with table=export_targets_table heading="Export Route Targets" %}
43
- {% include 'panel_table.html' with table=prefix_table heading="Assigned Prefixes" %}
44
- {% include 'panel_table.html' with table=device_table heading="Assigned Devices" %}
41
+ {% include 'utilities/obj_table.html' with table=import_targets_table table_template='panel_table.html' heading="Import Route Targets" %}
42
+ {% include 'utilities/obj_table.html' with table=export_targets_table table_template='panel_table.html' heading="Export Route Targets" %}
45
43
  {% endblock content_right_page %}
44
+
45
+ {% block content_full_width_page %}
46
+ {% include 'utilities/obj_table.html' with table=prefix_table table_template='panel_table.html' heading="Assigned Prefixes" %}
47
+ {% include 'utilities/obj_table.html' with table=device_table table_template='panel_table.html' heading="Assigned Devices" %}
48
+ {% endblock content_full_width_page %}