nautobot 3.0.3__py3-none-any.whl → 3.0.5__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.
Files changed (405) hide show
  1. nautobot/core/authentication.py +0 -1
  2. nautobot/core/celery/schedulers.py +1 -3
  3. nautobot/core/cli/__init__.py +81 -39
  4. nautobot/core/settings.yaml +12 -4
  5. nautobot/core/tables.py +28 -17
  6. nautobot/core/templates/graphene/graphiql.html +3 -5
  7. nautobot/core/templates/inc/javascript.html +5 -10
  8. nautobot/core/templates/inc/media.html +5 -4
  9. nautobot/core/templates/inc/media_failure.html +73 -0
  10. nautobot/core/templates/media_failure.html +1 -0
  11. nautobot/core/tests/test_cli.py +120 -1
  12. nautobot/core/tests/test_templatetags_helpers.py +9 -9
  13. nautobot/core/ui/object_detail.py +1 -0
  14. nautobot/dcim/forms.py +1 -0
  15. nautobot/dcim/tables/devices.py +6 -5
  16. nautobot/dcim/tables/template_code.py +8 -4
  17. nautobot/dcim/templates/dcim/platform_create.html +3 -4
  18. nautobot/dcim/tests/test_tables.py +5 -6
  19. nautobot/dcim/views.py +6 -7
  20. nautobot/extras/models/jobs.py +7 -1
  21. nautobot/extras/signals.py +143 -113
  22. nautobot/extras/tables.py +3 -3
  23. nautobot/extras/templates/extras/inc/jobresult_js.html +1 -2
  24. nautobot/extras/templates/extras/scheduledjob.html +3 -1
  25. nautobot/extras/tests/test_customfields.py +75 -9
  26. nautobot/extras/tests/test_utils.py +116 -1
  27. nautobot/extras/utils.py +18 -16
  28. nautobot/extras/views.py +2 -14
  29. nautobot/ipam/apps.py +1 -0
  30. nautobot/ipam/filters.py +58 -3
  31. nautobot/ipam/tables.py +8 -4
  32. nautobot/ipam/tests/test_filters.py +55 -0
  33. nautobot/project-static/dist/css/nautobot.css +1 -1
  34. nautobot/project-static/dist/css/nautobot.css.map +1 -1
  35. nautobot/project-static/docs/404.html +64 -8
  36. nautobot/project-static/docs/apps/index.html +64 -8
  37. nautobot/project-static/docs/apps/nautobot-apps.html +64 -8
  38. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +64 -8
  39. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +64 -8
  40. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +64 -8
  41. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +64 -8
  42. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +64 -8
  43. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +64 -8
  44. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +64 -8
  45. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +64 -8
  46. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +64 -8
  47. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +64 -8
  48. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +64 -8
  49. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +64 -8
  50. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +64 -8
  51. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +64 -8
  52. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +64 -8
  53. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +64 -8
  54. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +64 -8
  55. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +64 -8
  56. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +64 -8
  57. nautobot/project-static/docs/code-reference/nautobot/apps/templatetags.html +64 -8
  58. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +64 -8
  59. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +64 -8
  60. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +64 -8
  61. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +64 -8
  62. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +64 -8
  63. nautobot/project-static/docs/development/apps/api/configuration-view.html +64 -8
  64. nautobot/project-static/docs/development/apps/api/database-backend-config.html +64 -8
  65. nautobot/project-static/docs/development/apps/api/models/django-admin.html +67 -11
  66. nautobot/project-static/docs/development/apps/api/models/global-search.html +64 -8
  67. nautobot/project-static/docs/development/apps/api/models/graphql.html +64 -8
  68. nautobot/project-static/docs/development/apps/api/models/index.html +64 -8
  69. nautobot/project-static/docs/development/apps/api/models/queryset.html +13440 -0
  70. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +64 -8
  71. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +64 -8
  72. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +64 -8
  73. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +64 -8
  74. nautobot/project-static/docs/development/apps/api/platform-features/index.html +64 -8
  75. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +64 -8
  76. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +64 -8
  77. nautobot/project-static/docs/development/apps/api/platform-features/prepopulating-data.html +64 -8
  78. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +64 -8
  79. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +64 -8
  80. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +64 -8
  81. nautobot/project-static/docs/development/apps/api/prometheus.html +64 -8
  82. nautobot/project-static/docs/development/apps/api/setup.html +64 -8
  83. nautobot/project-static/docs/development/apps/api/testing.html +64 -8
  84. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +64 -8
  85. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +64 -8
  86. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +64 -8
  87. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +64 -8
  88. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +64 -8
  89. nautobot/project-static/docs/development/apps/api/views/base-template.html +64 -8
  90. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +64 -8
  91. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +64 -8
  92. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +64 -8
  93. nautobot/project-static/docs/development/apps/api/views/index.html +67 -11
  94. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +64 -8
  95. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +64 -8
  96. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +64 -8
  97. nautobot/project-static/docs/development/apps/api/views/notes.html +64 -8
  98. nautobot/project-static/docs/development/apps/api/views/rest-api.html +64 -8
  99. nautobot/project-static/docs/development/apps/api/views/urls.html +64 -8
  100. nautobot/project-static/docs/development/apps/index.html +64 -8
  101. nautobot/project-static/docs/development/apps/migration/code-updates.html +64 -8
  102. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +64 -8
  103. nautobot/project-static/docs/development/apps/migration/from-v1.html +64 -8
  104. nautobot/project-static/docs/development/apps/migration/from-v2/migrating-v2-to-v3.html +64 -8
  105. nautobot/project-static/docs/development/apps/migration/from-v2/new-nautobot-custom-ui-apis.html +64 -8
  106. nautobot/project-static/docs/development/apps/migration/from-v2/overview.html +68 -8
  107. nautobot/project-static/docs/development/apps/migration/from-v2/upgrading-from-bootstrap-v3-to-v5.html +64 -8
  108. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +64 -8
  109. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +64 -8
  110. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +64 -8
  111. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +64 -8
  112. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +64 -8
  113. nautobot/project-static/docs/development/apps/migration/ui-component-framework/breadcrumbs-titles.html +64 -8
  114. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +64 -8
  115. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +64 -8
  116. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +64 -8
  117. nautobot/project-static/docs/development/apps/porting-from-netbox.html +64 -8
  118. nautobot/project-static/docs/development/core/application-registry.html +64 -8
  119. nautobot/project-static/docs/development/core/best-practices.html +64 -8
  120. nautobot/project-static/docs/development/core/caching.html +64 -8
  121. nautobot/project-static/docs/development/core/controllers.html +64 -8
  122. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +64 -8
  123. nautobot/project-static/docs/development/core/docs-media-standards.html +64 -8
  124. nautobot/project-static/docs/development/core/generic-views.html +64 -8
  125. nautobot/project-static/docs/development/core/getting-started.html +64 -8
  126. nautobot/project-static/docs/development/core/homepage.html +64 -8
  127. nautobot/project-static/docs/development/core/index.html +64 -8
  128. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +64 -8
  129. nautobot/project-static/docs/development/core/model-checklist.html +64 -8
  130. nautobot/project-static/docs/development/core/model-features.html +64 -8
  131. nautobot/project-static/docs/development/core/natural-keys.html +64 -8
  132. nautobot/project-static/docs/development/core/navigation-menu.html +64 -8
  133. nautobot/project-static/docs/development/core/release-checklist.html +66 -8
  134. nautobot/project-static/docs/development/core/role-internals.html +64 -8
  135. nautobot/project-static/docs/development/core/settings.html +64 -8
  136. nautobot/project-static/docs/development/core/style-guide.html +64 -8
  137. nautobot/project-static/docs/development/core/templates.html +64 -8
  138. nautobot/project-static/docs/development/core/testing.html +64 -8
  139. nautobot/project-static/docs/development/core/ui-best-practices.html +64 -8
  140. nautobot/project-static/docs/development/core/ui-component-framework.html +64 -8
  141. nautobot/project-static/docs/development/core/user-preferences.html +64 -8
  142. nautobot/project-static/docs/development/index.html +64 -8
  143. nautobot/project-static/docs/development/jobs/getting-started.html +64 -8
  144. nautobot/project-static/docs/development/jobs/index.html +64 -8
  145. nautobot/project-static/docs/development/jobs/installation.html +64 -8
  146. nautobot/project-static/docs/development/jobs/job-extensions.html +64 -8
  147. nautobot/project-static/docs/development/jobs/job-logging.html +64 -8
  148. nautobot/project-static/docs/development/jobs/job-patterns.html +64 -8
  149. nautobot/project-static/docs/development/jobs/job-structure.html +64 -8
  150. nautobot/project-static/docs/development/jobs/migration/from-v1.html +64 -8
  151. nautobot/project-static/docs/development/jobs/testing.html +64 -8
  152. nautobot/project-static/docs/index.html +64 -8
  153. nautobot/project-static/docs/overview/application_stack.html +64 -8
  154. nautobot/project-static/docs/overview/design_philosophy.html +64 -8
  155. nautobot/project-static/docs/release-notes/index.html +64 -8
  156. nautobot/project-static/docs/release-notes/version-1.0.html +64 -8
  157. nautobot/project-static/docs/release-notes/version-1.1.html +64 -8
  158. nautobot/project-static/docs/release-notes/version-1.2.html +65 -9
  159. nautobot/project-static/docs/release-notes/version-1.3.html +64 -8
  160. nautobot/project-static/docs/release-notes/version-1.4.html +64 -8
  161. nautobot/project-static/docs/release-notes/version-1.5.html +64 -8
  162. nautobot/project-static/docs/release-notes/version-1.6.html +64 -8
  163. nautobot/project-static/docs/release-notes/version-2.0.html +64 -8
  164. nautobot/project-static/docs/release-notes/version-2.1.html +64 -8
  165. nautobot/project-static/docs/release-notes/version-2.2.html +64 -8
  166. nautobot/project-static/docs/release-notes/version-2.3.html +64 -8
  167. nautobot/project-static/docs/release-notes/version-2.4.html +584 -8
  168. nautobot/project-static/docs/release-notes/version-3.0.html +467 -8
  169. nautobot/project-static/docs/search/search_index.json +1 -1
  170. nautobot/project-static/docs/sitemap.xml +337 -329
  171. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  172. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +64 -8
  173. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +64 -8
  174. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +64 -8
  175. nautobot/project-static/docs/user-guide/administration/configuration/index.html +64 -8
  176. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +64 -8
  177. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +75 -12
  178. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +64 -8
  179. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +64 -8
  180. nautobot/project-static/docs/user-guide/administration/guides/docker.html +64 -8
  181. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +64 -8
  182. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +64 -8
  183. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +64 -8
  184. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +64 -8
  185. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +64 -8
  186. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +64 -8
  187. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +64 -8
  188. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +64 -8
  189. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +64 -8
  190. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +72 -9
  191. nautobot/project-static/docs/user-guide/administration/installation/index.html +64 -8
  192. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +64 -8
  193. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +64 -8
  194. nautobot/project-static/docs/user-guide/administration/installation/services.html +64 -8
  195. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +65 -9
  196. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +64 -8
  197. nautobot/project-static/docs/user-guide/administration/security/index.html +64 -8
  198. nautobot/project-static/docs/user-guide/administration/security/notices.html +64 -8
  199. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +64 -8
  200. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +64 -8
  201. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +67 -11
  202. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +64 -8
  203. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +64 -8
  204. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +64 -8
  205. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +64 -8
  206. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +64 -8
  207. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +64 -8
  208. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +64 -8
  209. nautobot/project-static/docs/user-guide/administration/upgrading/from-v2/index.html +68 -8
  210. nautobot/project-static/docs/user-guide/administration/upgrading/postgresql.html +13391 -0
  211. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +125 -15
  212. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +64 -8
  213. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +64 -8
  214. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +64 -8
  215. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +64 -8
  216. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +64 -8
  217. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +64 -8
  218. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +64 -8
  219. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +64 -8
  220. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +64 -8
  221. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +64 -8
  222. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +64 -8
  223. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +64 -8
  224. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +64 -8
  225. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +64 -8
  226. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +64 -8
  227. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +64 -8
  228. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +64 -8
  229. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +64 -8
  230. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +64 -8
  231. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +64 -8
  232. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +64 -8
  233. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +64 -8
  234. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +64 -8
  235. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +64 -8
  236. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +64 -8
  237. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +64 -8
  238. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +64 -8
  239. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +64 -8
  240. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +64 -8
  241. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +64 -8
  242. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +64 -8
  243. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +64 -8
  244. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +64 -8
  245. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +64 -8
  246. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +64 -8
  247. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +64 -8
  248. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +64 -8
  249. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +64 -8
  250. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +64 -8
  251. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +64 -8
  252. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +64 -8
  253. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +64 -8
  254. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +64 -8
  255. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +64 -8
  256. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +64 -8
  257. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +64 -8
  258. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +64 -8
  259. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +64 -8
  260. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +64 -8
  261. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +64 -8
  262. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +64 -8
  263. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +64 -8
  264. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +64 -8
  265. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +64 -8
  266. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +64 -8
  267. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +64 -8
  268. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +64 -8
  269. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +64 -8
  270. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +64 -8
  271. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +64 -8
  272. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +64 -8
  273. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +64 -8
  274. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +64 -8
  275. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +64 -8
  276. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +64 -8
  277. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +64 -8
  278. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +64 -8
  279. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +64 -8
  280. nautobot/project-static/docs/user-guide/core-data-model/load-balancers/certificateprofile.html +64 -8
  281. nautobot/project-static/docs/user-guide/core-data-model/load-balancers/healthcheckmonitor.html +64 -8
  282. nautobot/project-static/docs/user-guide/core-data-model/load-balancers/index.html +64 -8
  283. nautobot/project-static/docs/user-guide/core-data-model/load-balancers/loadbalancerpool.html +64 -8
  284. nautobot/project-static/docs/user-guide/core-data-model/load-balancers/loadbalancerpoolmember.html +64 -8
  285. nautobot/project-static/docs/user-guide/core-data-model/load-balancers/virtualserver.html +64 -8
  286. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +64 -8
  287. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +64 -8
  288. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +64 -8
  289. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +64 -8
  290. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +64 -8
  291. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +64 -8
  292. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +64 -8
  293. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +64 -8
  294. nautobot/project-static/docs/user-guide/core-data-model/vpn/index.html +64 -8
  295. nautobot/project-static/docs/user-guide/core-data-model/vpn/vpn.html +64 -8
  296. nautobot/project-static/docs/user-guide/core-data-model/vpn/vpnphase1policy.html +64 -8
  297. nautobot/project-static/docs/user-guide/core-data-model/vpn/vpnphase2policy.html +64 -8
  298. nautobot/project-static/docs/user-guide/core-data-model/vpn/vpnprofile.html +64 -8
  299. nautobot/project-static/docs/user-guide/core-data-model/vpn/vpntunnel.html +64 -8
  300. nautobot/project-static/docs/user-guide/core-data-model/vpn/vpntunnelendpoint.html +64 -8
  301. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +64 -8
  302. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +64 -8
  303. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +64 -8
  304. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +64 -8
  305. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +64 -8
  306. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +64 -8
  307. nautobot/project-static/docs/user-guide/feature-guides/data-compliance.html +64 -8
  308. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +64 -8
  309. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +64 -8
  310. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +64 -8
  311. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +64 -8
  312. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +64 -8
  313. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +64 -8
  314. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +64 -8
  315. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +75 -12
  316. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +85 -17
  317. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +64 -8
  318. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +64 -8
  319. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/12-add-tenant-dark.png +0 -0
  320. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/12-add-tenant-light.png +0 -0
  321. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/13-assign-tenant-to-device-dark.png +0 -0
  322. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/13-assign-tenant-to-device-light.png +0 -0
  323. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/14-assign-tenant-to-device-2-dark.png +0 -0
  324. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/14-assign-tenant-to-device-2-light.png +0 -0
  325. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/22-create-vlans-dark.png +0 -0
  326. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/22-create-vlans-light.png +0 -0
  327. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/23-create-vlans-2-dark.png +0 -0
  328. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/23-create-vlans-2-light.png +0 -0
  329. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/24-vlan-main-page-dark.png +0 -0
  330. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/24-vlan-main-page-light.png +0 -0
  331. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/25-add-vlan-to-interface-dark.png +0 -0
  332. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/25-add-vlan-to-interface-light.png +0 -0
  333. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/26-add-vlan-to-interface-2-dark.png +0 -0
  334. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/26-add-vlan-to-interface-2-light.png +0 -0
  335. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +64 -8
  336. nautobot/project-static/docs/user-guide/feature-guides/load-balancers.html +64 -8
  337. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +64 -8
  338. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +64 -8
  339. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +64 -8
  340. nautobot/project-static/docs/user-guide/index.html +64 -8
  341. nautobot/project-static/docs/user-guide/platform-functionality/approval-workflow.html +64 -8
  342. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +64 -8
  343. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +64 -8
  344. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +64 -8
  345. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +64 -8
  346. nautobot/project-static/docs/user-guide/platform-functionality/data-validation.html +64 -8
  347. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +64 -8
  348. nautobot/project-static/docs/user-guide/platform-functionality/echarts.html +64 -8
  349. nautobot/project-static/docs/user-guide/platform-functionality/events.html +64 -8
  350. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +64 -8
  351. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +64 -8
  352. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +64 -8
  353. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +64 -8
  354. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +64 -8
  355. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +64 -8
  356. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +64 -8
  357. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +64 -8
  358. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +64 -8
  359. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +64 -8
  360. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +64 -8
  361. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +64 -8
  362. nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +64 -8
  363. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +64 -8
  364. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +64 -8
  365. nautobot/project-static/docs/user-guide/platform-functionality/note.html +64 -8
  366. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +64 -8
  367. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +64 -8
  368. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +64 -8
  369. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +64 -8
  370. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +116 -34
  371. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +64 -8
  372. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +64 -8
  373. nautobot/project-static/docs/user-guide/platform-functionality/role.html +64 -8
  374. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +64 -8
  375. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +64 -8
  376. nautobot/project-static/docs/user-guide/platform-functionality/status.html +64 -8
  377. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +64 -8
  378. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +64 -8
  379. nautobot/project-static/docs/user-guide/platform-functionality/user-interface/configurablecolumns.html +64 -8
  380. nautobot/project-static/docs/user-guide/platform-functionality/user-interface/savedview.html +64 -8
  381. nautobot/project-static/docs/user-guide/platform-functionality/user-interface/search.html +64 -8
  382. nautobot/project-static/docs/user-guide/platform-functionality/users/groups.html +64 -8
  383. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +64 -8
  384. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +64 -8
  385. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +64 -8
  386. nautobot/tenancy/tables.py +1 -1
  387. nautobot/ui/package-lock.json +36 -36
  388. nautobot/ui/package.json +3 -3
  389. nautobot/ui/src/scss/nautobot.scss +2 -1
  390. nautobot/users/models.py +33 -0
  391. nautobot/users/tests/test_models.py +83 -0
  392. {nautobot-3.0.3.dist-info → nautobot-3.0.5.dist-info}/METADATA +4 -4
  393. {nautobot-3.0.3.dist-info → nautobot-3.0.5.dist-info}/RECORD +397 -386
  394. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/12-add-tenant.png +0 -0
  395. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/13-assign-tenant-to-device.png +0 -0
  396. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/14-assign-tenant-to-device-2.png +0 -0
  397. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/22-create-vlans.png +0 -0
  398. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/23-create-vlans-2.png +0 -0
  399. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/24-vlan-main-page.png +0 -0
  400. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/25-add-vlan-to-interface.png +0 -0
  401. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/26-add-vlan-to-interface-2.png +0 -0
  402. {nautobot-3.0.3.dist-info → nautobot-3.0.5.dist-info}/LICENSE.txt +0 -0
  403. {nautobot-3.0.3.dist-info → nautobot-3.0.5.dist-info}/NOTICE +0 -0
  404. {nautobot-3.0.3.dist-info → nautobot-3.0.5.dist-info}/WHEEL +0 -0
  405. {nautobot-3.0.3.dist-info → nautobot-3.0.5.dist-info}/entry_points.txt +0 -0
@@ -48,23 +48,23 @@ class NautobotTemplatetagsHelperTest(TestCase):
48
48
  self.assertEqual(
49
49
  helpers.hyperlinked_email("admin@example.com"), '<a href="mailto:admin@example.com">admin@example.com</a>'
50
50
  )
51
- self.assertEqual(helpers.hyperlinked_email(None), '<span class="text-secondary">&mdash;</span>')
51
+ self.assertEqual(helpers.hyperlinked_email(None), helpers.HTML_NONE)
52
52
 
53
53
  def test_hyperlinked_phone_number(self):
54
54
  self.assertEqual(helpers.hyperlinked_phone_number("555-1234"), '<a href="tel:555-1234">555-1234</a>')
55
- self.assertEqual(helpers.hyperlinked_phone_number(None), '<span class="text-secondary">&mdash;</span>')
55
+ self.assertEqual(helpers.hyperlinked_phone_number(None), helpers.HTML_NONE)
56
56
 
57
57
  def test_placeholder(self):
58
- self.assertEqual(helpers.placeholder(None), '<span class="text-secondary">&mdash;</span>')
59
- self.assertEqual(helpers.placeholder([]), '<span class="text-secondary">&mdash;</span>')
58
+ self.assertEqual(helpers.placeholder(None), helpers.HTML_NONE)
59
+ self.assertEqual(helpers.placeholder([]), helpers.HTML_NONE)
60
60
  self.assertEqual(helpers.placeholder("something"), "something")
61
61
 
62
62
  def test_pre_tag(self):
63
- self.assertEqual(helpers.pre_tag(None), '<span class="text-secondary">&mdash;</span>')
63
+ self.assertEqual(helpers.pre_tag(None), helpers.HTML_NONE)
64
64
  self.assertEqual(helpers.pre_tag([]), "<pre>[]</pre>")
65
65
  self.assertEqual(helpers.pre_tag("something"), "<pre>something</pre>")
66
- self.assertEqual(helpers.pre_tag("", format_empty_value=False), '<span class="text-secondary">&mdash;</span>')
67
- self.assertEqual(helpers.pre_tag([], format_empty_value=False), '<span class="text-secondary">&mdash;</span>')
66
+ self.assertEqual(helpers.pre_tag("", format_empty_value=False), helpers.HTML_NONE)
67
+ self.assertEqual(helpers.pre_tag([], format_empty_value=False), helpers.HTML_NONE)
68
68
  self.assertEqual(helpers.pre_tag("something", format_empty_value=False), "<pre>something</pre>")
69
69
 
70
70
  def test_add_html_id(self):
@@ -263,7 +263,7 @@ class NautobotTemplatetagsHelperTest(TestCase):
263
263
  helpers.render_boolean(value),
264
264
  '<span class="text-danger"><i class="mdi mdi-close-thick" title="No"></i></span>',
265
265
  )
266
- self.assertEqual(helpers.render_boolean(None), '<span class="text-secondary">&mdash;</span>')
266
+ self.assertEqual(helpers.render_boolean(None), helpers.HTML_NONE)
267
267
 
268
268
  def test_hyperlinked_object_with_color(self):
269
269
  vlan_with_role = VLAN.objects.filter(role__isnull=False).first()
@@ -276,7 +276,7 @@ class NautobotTemplatetagsHelperTest(TestCase):
276
276
  f'<span class="badge" style="color: {fbcolor}; background-color: #{color}">{display}</span>',
277
277
  )
278
278
  # Assert when obj is None
279
- self.assertEqual(helpers.hyperlinked_object_with_color(obj=None), '<span class="text-secondary">&mdash;</span>')
279
+ self.assertEqual(helpers.hyperlinked_object_with_color(obj=None), helpers.HTML_NONE)
280
280
 
281
281
  @tag("example_app")
282
282
  @override_settings(BANNER_TOP="¡Hola, mundo!")
@@ -1917,6 +1917,7 @@ class _ObjectCustomFieldsPanel(GroupedKeyValueTablePanel):
1917
1917
 
1918
1918
  def render_value(self, key, value, context: Context):
1919
1919
  """Render a given custom field value appropriately depending on what type of custom field it is."""
1920
+ # TODO: this logic could be unified with CustomFieldColumn.render()?
1920
1921
  cf = key
1921
1922
  if cf.type == CustomFieldTypeChoices.TYPE_BOOLEAN:
1922
1923
  return render_boolean(value)
nautobot/dcim/forms.py CHANGED
@@ -4516,6 +4516,7 @@ class CableFilterForm(BootstrapMixin, StatusModelFilterFormMixin, forms.Form):
4516
4516
  color = forms.CharField(max_length=6, required=False, widget=ColorSelect()) # RGB color code
4517
4517
  device = DynamicModelMultipleChoiceField(
4518
4518
  queryset=Device.objects.all(),
4519
+ to_field_name="name",
4519
4520
  required=False,
4520
4521
  label="Device",
4521
4522
  query_params={
@@ -11,7 +11,7 @@ from nautobot.core.tables import (
11
11
  TagColumn,
12
12
  ToggleColumn,
13
13
  )
14
- from nautobot.core.templatetags.helpers import humanize_speed
14
+ from nautobot.core.templatetags.helpers import HTML_NONE, humanize_speed
15
15
  from nautobot.dcim.models import (
16
16
  ConsolePort,
17
17
  ConsoleServerPort,
@@ -204,7 +204,7 @@ class DeviceTable(StatusTableMixin, RoleTableMixin, BaseTable):
204
204
  vc_priority = tables.Column(verbose_name="VC Priority")
205
205
  device_redundancy_group = tables.Column(linkify=True)
206
206
  device_redundancy_group_priority = tables.TemplateColumn(
207
- template_code="""{% if record.device_redundancy_group %}<span class="badge badge-default">{{ record.device_redundancy_group_priority|default:'None' }}</span>{% else %}{% endif %}"""
207
+ template_code="""{% if record.device_redundancy_group %}<span class="badge badge-default">{{ record.device_redundancy_group_priority|default:'None' }}</span>{% else %}<span class="text-secondary">—</span>{% endif %}"""
208
208
  )
209
209
  controller_managed_device_group = tables.Column(linkify=True, verbose_name="Device Group")
210
210
  software_version = tables.Column(linkify=True, verbose_name="Software Version")
@@ -263,7 +263,7 @@ class DeviceTable(StatusTableMixin, RoleTableMixin, BaseTable):
263
263
  def render_capabilities(self, value):
264
264
  """Render capabilities."""
265
265
  if not value:
266
- return format_html("&mdash;")
266
+ return HTML_NONE
267
267
  return format_html_join(" ", '<span class="badge bg-secondary">{}</span>', ((v,) for v in value))
268
268
 
269
269
 
@@ -1260,6 +1260,7 @@ class DeviceRedundancyGroupTable(BaseTable):
1260
1260
  fields = (
1261
1261
  "pk",
1262
1262
  "name",
1263
+ "description",
1263
1264
  "status",
1264
1265
  "failover_strategy",
1265
1266
  "controller_count",
@@ -1504,7 +1505,7 @@ class ControllerTable(StatusTableMixin, RoleTableMixin, BaseTable):
1504
1505
  def render_capabilities(self, value):
1505
1506
  """Render capabilities."""
1506
1507
  if not value:
1507
- return format_html("&mdash;")
1508
+ return HTML_NONE
1508
1509
  return format_html_join(" ", '<span class="badge bg-secondary">{}</span>', ((v,) for v in value))
1509
1510
 
1510
1511
 
@@ -1568,7 +1569,7 @@ class ControllerManagedDeviceGroupTable(BaseTable):
1568
1569
  def render_capabilities(self, value):
1569
1570
  """Render capabilities."""
1570
1571
  if not value:
1571
- return format_html("&mdash;")
1572
+ return HTML_NONE
1572
1573
  return format_html_join(" ", '<span class="badge bg-secondary">{}</span>', ((v,) for v in value))
1573
1574
 
1574
1575
 
@@ -4,7 +4,7 @@ CABLETERMINATION = """
4
4
  <i class="mdi mdi-chevron-right"></i>
5
5
  <a href="{{ value.get_absolute_url }}">{{ value }}</a>
6
6
  {% else %}
7
- &mdash;
7
+ <span class="text-secondary">&mdash;</span>
8
8
  {% endif %}
9
9
  """
10
10
 
@@ -26,12 +26,16 @@ PATHENDPOINT = """
26
26
  {% endfor %}
27
27
  {% endwith %}
28
28
  {% else %}
29
- &mdash;
29
+ <span class="text-secondary">&mdash;</span>
30
30
  {% endif %}
31
31
  """
32
32
 
33
33
  CABLE_LENGTH = """
34
- {% if record.length %}{{ record.length }} {{ record.get_length_unit_display }}{% else %}&mdash;{% endif %}
34
+ {% if record.length %}
35
+ {{ record.length }} {{ record.get_length_unit_display }}
36
+ {% else %}
37
+ <span class="text-secondary">&mdash;</span>
38
+ {% endif %}
35
39
  """
36
40
 
37
41
  CABLE_TERMINATION_PARENT = """
@@ -83,7 +87,7 @@ INTERFACE_TAGGED_VLANS = """
83
87
  {% elif record.mode == 'tagged-all' %}
84
88
  All
85
89
  {% else %}
86
- &mdash;
90
+ <span class="text-secondary">&mdash;</span>
87
91
  {% endif %}
88
92
  """
89
93
 
@@ -6,12 +6,11 @@
6
6
 
7
7
  {% block form_fields %}
8
8
  {% render_field form.name %}
9
- {% render_field form.slug %}
10
9
  {% render_field form.manufacturer %}
11
10
 
12
- <div class="mb-10 d-flex justify-content-center{% if form.network_driver.errors %} has-error{% endif %}">
13
- <label class="col-lg-3 col-form-label" for="id_network_driver">Network driver</label>
14
- <div class="col-lg-9">
11
+ <div class="mb-10 d-md-flex justify-content-center{% if form.network_driver.errors %} has-error{% endif %}">
12
+ <label class="col-md-3 col-form-label" for="id_network_driver">Network driver</label>
13
+ <div class="col-md-9">
15
14
  {{ form.network_driver }}
16
15
  <span class="form-text">
17
16
  The <a href="https://netutils.readthedocs.io/en/latest/user/lib_use_cases_lib_mapper/">normalized network driver</a> to use when interacting with devices
@@ -1,5 +1,6 @@
1
1
  from django.test import TestCase
2
2
 
3
+ from nautobot.core.templatetags import helpers
3
4
  from nautobot.dcim.choices import InterfaceDuplexChoices, InterfaceSpeedChoices, InterfaceTypeChoices
4
5
  from nautobot.dcim.models import Device, DeviceType, Interface, InterfaceTemplate, Location, LocationType, Manufacturer
5
6
  from nautobot.dcim.tables.devices import DeviceModuleInterfaceTable, InterfaceTable
@@ -52,7 +53,6 @@ class InterfaceTableRenderMixin:
52
53
 
53
54
  def test_render_speed_duplex_with_none(self):
54
55
  """Test that the table handles None speed value and renders an emdash."""
55
- emdash = "\u2014"
56
56
  interface = Interface.objects.create(
57
57
  device=self.device,
58
58
  name="eth1",
@@ -67,8 +67,8 @@ class InterfaceTableRenderMixin:
67
67
  rendered_speed = bound_row.get_cell("speed")
68
68
  rendered_duplex = bound_row.get_cell("duplex")
69
69
 
70
- self.assertEqual(rendered_speed, emdash)
71
- self.assertEqual(rendered_duplex, emdash)
70
+ self.assertEqual(rendered_speed, helpers.HTML_NONE)
71
+ self.assertEqual(rendered_duplex, helpers.HTML_NONE)
72
72
 
73
73
  def test_render_speed_various(self):
74
74
  """Test that the table correctly humanizes various speed values."""
@@ -146,7 +146,6 @@ class InterfaceTemplateTableTestCase(TestCase):
146
146
  self.assertEqual(rendered_duplex, "Full")
147
147
 
148
148
  def test_render_speed_duplex_with_none(self):
149
- emdash = "\u2014"
150
149
  interface_template = InterfaceTemplate.objects.create(
151
150
  device_type=self.device_type,
152
151
  name="tmpl-eth1",
@@ -156,5 +155,5 @@ class InterfaceTemplateTableTestCase(TestCase):
156
155
  bound_row = table.rows[0]
157
156
  rendered_speed = bound_row.get_cell("speed") # pylint: disable=no-member
158
157
  rendered_duplex = bound_row.get_cell("duplex") # pylint: disable=no-member
159
- self.assertEqual(rendered_speed, emdash)
160
- self.assertEqual(rendered_duplex, emdash)
158
+ self.assertEqual(rendered_speed, helpers.HTML_NONE)
159
+ self.assertEqual(rendered_duplex, helpers.HTML_NONE)
nautobot/dcim/views.py CHANGED
@@ -35,7 +35,6 @@ from nautobot.core.exceptions import AbortTransaction
35
35
  from nautobot.core.forms import BulkRenameForm, ConfirmationForm, ImportForm, restrict_form_fields
36
36
  from nautobot.core.models.querysets import count_related
37
37
  from nautobot.core.templatetags import helpers
38
- from nautobot.core.templatetags.helpers import bettertitle, has_perms
39
38
  from nautobot.core.ui import object_detail
40
39
  from nautobot.core.ui.breadcrumbs import (
41
40
  AncestorsInstanceBreadcrumbItem,
@@ -559,14 +558,14 @@ class MigrateLocationDataToContactView(generic.ObjectEditView):
559
558
  migrate_action = request.POST.get("action")
560
559
  try:
561
560
  with transaction.atomic():
562
- if not has_perms(request.user, ["extras.add_contactassociation"]):
561
+ if not helpers.has_perms(request.user, ["extras.add_contactassociation"]):
563
562
  raise PermissionDenied(
564
563
  "ObjectPermission extras.add_contactassociation is needed to perform this action"
565
564
  )
566
565
  contact = None
567
566
  team = None
568
567
  if migrate_action == LocationDataToContactActionChoices.CREATE_AND_ASSIGN_NEW_CONTACT:
569
- if not has_perms(request.user, ["extras.add_contact"]):
568
+ if not helpers.has_perms(request.user, ["extras.add_contact"]):
570
569
  raise PermissionDenied("ObjectPermission extras.add_contact is needed to perform this action")
571
570
  contact = Contact(
572
571
  name=request.POST.get("name"),
@@ -577,7 +576,7 @@ class MigrateLocationDataToContactView(generic.ObjectEditView):
577
576
  # Trigger permission check
578
577
  Contact.objects.restrict(request.user, "view").get(pk=contact.pk)
579
578
  elif migrate_action == LocationDataToContactActionChoices.CREATE_AND_ASSIGN_NEW_TEAM:
580
- if not has_perms(request.user, ["extras.add_team"]):
579
+ if not helpers.has_perms(request.user, ["extras.add_team"]):
581
580
  raise PermissionDenied("ObjectPermission extras.add_team is needed to perform this action")
582
581
  team = Team(
583
582
  name=request.POST.get("name"),
@@ -958,7 +957,7 @@ class DeviceTypeFieldsPanel(object_detail.ObjectFieldsPanel):
958
957
  image.url,
959
958
  image.name,
960
959
  )
961
- return format_html('<span class="text-secondary">&mdash;</span>')
960
+ return helpers.HTML_NONE
962
961
 
963
962
  return super().render_value(key, value, context)
964
963
 
@@ -2309,7 +2308,7 @@ class DeviceComponentPageMixin:
2309
2308
  view_name=device_breadcrumb_url,
2310
2309
  should_render=lambda c: c["object"].device is not None,
2311
2310
  reverse_kwargs=lambda c: {"pk": c["object"].device.pk},
2312
- label=lambda c: bettertitle(c["object"]._meta.verbose_name_plural),
2311
+ label=lambda c: helpers.bettertitle(c["object"]._meta.verbose_name_plural),
2313
2312
  ),
2314
2313
  )
2315
2314
 
@@ -2319,7 +2318,7 @@ class DeviceComponentPageMixin:
2319
2318
  view_name=module_breadcrumb_url,
2320
2319
  should_render=lambda c: c["object"].device is None,
2321
2320
  reverse_kwargs=lambda c: {"pk": c["object"].module.pk},
2322
- label=lambda c: bettertitle(c["object"]._meta.verbose_name_plural),
2321
+ label=lambda c: helpers.bettertitle(c["object"]._meta.verbose_name_plural),
2323
2322
  ),
2324
2323
  )
2325
2324
 
@@ -253,6 +253,7 @@ class Job(PrimaryModel):
253
253
  )
254
254
  objects = BaseManager.from_queryset(JobQuerySet)()
255
255
  is_data_compliance_model = False
256
+ is_version_controlled = False
256
257
 
257
258
  documentation_static_path = "docs/user-guide/platform-functionality/jobs/models.html"
258
259
 
@@ -533,6 +534,7 @@ class JobLogEntry(BaseModel):
533
534
 
534
535
  is_metadata_associable_model = False
535
536
  is_data_compliance_model = False
537
+ is_version_controlled = False
536
538
 
537
539
  documentation_static_path = "docs/user-guide/platform-functionality/jobs/models.html"
538
540
  hide_in_diff_view = True
@@ -585,6 +587,7 @@ class JobQueue(PrimaryModel):
585
587
 
586
588
  documentation_static_path = "docs/user-guide/platform-functionality/jobs/jobqueue.html"
587
589
  is_data_compliance_model = False
590
+ is_version_controlled = False
588
591
 
589
592
  class Meta:
590
593
  ordering = ["name"]
@@ -616,6 +619,7 @@ class JobQueueAssignment(BaseModel):
616
619
  job_queue = models.ForeignKey(JobQueue, on_delete=models.CASCADE, related_name="job_assignments")
617
620
  is_metadata_associable_model = False
618
621
  is_data_compliance_model = False
622
+ is_version_controlled = False
619
623
 
620
624
  class Meta:
621
625
  unique_together = ["job", "job_queue"]
@@ -695,6 +699,7 @@ class JobResult(SavedViewMixin, BaseModel, CustomFieldModel):
695
699
  documentation_static_path = "docs/user-guide/platform-functionality/jobs/models.html"
696
700
  hide_in_diff_view = True
697
701
  is_data_compliance_model = False
702
+ is_version_controlled = False
698
703
 
699
704
  def __init__(self, *args, **kwargs):
700
705
  super().__init__(*args, **kwargs)
@@ -897,7 +902,7 @@ class JobResult(SavedViewMixin, BaseModel, CustomFieldModel):
897
902
  # so that `run_kubernetes_job_and_return_job_result` is not executed again and the job will be run locally.
898
903
  if job_queue.queue_type == JobQueueTypeChoices.TYPE_KUBERNETES and not synchronous:
899
904
  # TODO: make this branch aware!
900
- return run_kubernetes_job_and_return_job_result(job_queue, job_result, json.dumps(job_kwargs))
905
+ return run_kubernetes_job_and_return_job_result(job_result, json.dumps(job_kwargs))
901
906
 
902
907
  job_celery_kwargs = {
903
908
  "nautobot_job_job_model_id": job_model.id,
@@ -1277,6 +1282,7 @@ class ScheduledJob(ApprovableModelMixin, BaseModel):
1277
1282
 
1278
1283
  documentation_static_path = "docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html"
1279
1284
  is_data_compliance_model = False
1285
+ is_version_controlled = False
1280
1286
 
1281
1287
  def __str__(self):
1282
1288
  return f"{self.name}: {self.interval}"
@@ -8,6 +8,7 @@ import uuid
8
8
 
9
9
  from db_file_storage.model_utils import delete_file
10
10
  from db_file_storage.storage import DatabaseFileStorage
11
+ from django.conf import settings
11
12
  from django.contrib.contenttypes.models import ContentType
12
13
  from django.core.cache import cache
13
14
  from django.core.exceptions import ValidationError
@@ -19,6 +20,7 @@ from django.utils import timezone
19
20
  from django_prometheus.models import model_deletes, model_inserts, model_updates
20
21
  import redis.exceptions
21
22
 
23
+ from nautobot.core.branching import BranchContext
22
24
  from nautobot.core.celery import app, import_jobs
23
25
  from nautobot.core.models import BaseModel
24
26
  from nautobot.core.utils.cache import construct_cache_key
@@ -214,6 +216,29 @@ def invalidate_gitrepository_provided_contents_cache(sender, **kwargs):
214
216
  cache.delete_pattern(f"{cache_key}(*)")
215
217
 
216
218
 
219
+ def _object_change_branch_name(instance):
220
+ """
221
+ Get the version-control branch name (if any) that needs to be switched to for ObjectChanges on a given instance.
222
+ """
223
+ if "nautobot_version_control" not in settings.PLUGINS:
224
+ return None
225
+
226
+ # When modifying non-version-controlled models, which only get committed to DOLT_DEFAULT_BRANCH,
227
+ # we need to ensure that the corresponding ObjectChange also is created there, even if we're otherwise working
228
+ # in a non-default branch at the moment. Failing to do so would result in a Dolt error on transaction commit:
229
+ # "Cannot commit changes on more than one branch / database"
230
+ from nautobot_version_control.constants import DOLT_DEFAULT_BRANCH # pylint: disable=import-error
231
+ from nautobot_version_control.utils import ( # pylint: disable=import-error
232
+ active_branch,
233
+ is_version_controlled_model,
234
+ )
235
+
236
+ if is_version_controlled_model(instance.__class__) or active_branch() == DOLT_DEFAULT_BRANCH:
237
+ return None # no need to switch branches
238
+
239
+ return DOLT_DEFAULT_BRANCH # need to switch temporarily to the default `main` branch for this record
240
+
241
+
217
242
  @receiver(post_save)
218
243
  @receiver(m2m_changed)
219
244
  def _handle_changed_object(sender, instance, raw=False, **kwargs):
@@ -242,60 +267,63 @@ def _handle_changed_object(sender, instance, raw=False, **kwargs):
242
267
 
243
268
  # Record an ObjectChange if applicable
244
269
  if hasattr(instance, "to_objectchange"):
270
+ branch_name = _object_change_branch_name(instance)
245
271
  user = change_context.get_user(instance)
246
- # save a copy of this instance's field cache so it can be restored after serialization
247
- # to prevent unexpected behavior when chaining multiple signal handlers
248
- original_cache = instance._state.fields_cache.copy()
249
-
250
- changed_object_type = ContentType.objects.get_for_model(instance)
251
- changed_object_id = instance.id
252
272
 
253
- # Generate a unique identifier for this change to stash in the change context
254
- # This is used for deferred change logging and for looking up related changes without querying the database
255
- unique_object_change_id = None
256
- if user is not None:
257
- unique_object_change_id = f"{changed_object_type.pk}__{changed_object_id}__{user.pk}"
258
- else:
259
- unique_object_change_id = f"{changed_object_type.pk}__{changed_object_id}"
260
-
261
- # If a change already exists for this change_id, user, and object, update it instead of creating a new one.
262
- # If the object was deleted then recreated with the same pk (don't do this), change the action to update.
263
- if unique_object_change_id in change_context.deferred_object_changes:
264
- related_changes = ObjectChange.objects.filter(
265
- changed_object_type=changed_object_type,
266
- changed_object_id=changed_object_id,
267
- user=user,
268
- request_id=change_context.change_id,
269
- )
270
-
271
- # Skip the database check when deferring object changes
272
- if not change_context.defer_object_changes and related_changes.exists():
273
- objectchange = instance.to_objectchange(action)
274
- if objectchange is not None:
275
- most_recent_change = related_changes.order_by("-time").first()
276
- if most_recent_change.action == ObjectChangeActionChoices.ACTION_DELETE:
277
- most_recent_change.action = ObjectChangeActionChoices.ACTION_UPDATE
278
- most_recent_change.object_data = objectchange.object_data
279
- most_recent_change.object_data_v2 = objectchange.object_data_v2
280
- most_recent_change.save()
273
+ with BranchContext(branch_name=branch_name, user=user, autocommit=False):
274
+ # save a copy of this instance's field cache so it can be restored after serialization
275
+ # to prevent unexpected behavior when chaining multiple signal handlers
276
+ original_cache = instance._state.fields_cache.copy()
277
+
278
+ changed_object_type = ContentType.objects.get_for_model(instance)
279
+ changed_object_id = instance.id
280
+
281
+ # Generate a unique identifier for this change to stash in the change context
282
+ # This is used for deferred change logging and for looking up related changes without querying the database
283
+ unique_object_change_id = None
284
+ if user is not None:
285
+ unique_object_change_id = f"{changed_object_type.pk}__{changed_object_id}__{user.pk}"
286
+ else:
287
+ unique_object_change_id = f"{changed_object_type.pk}__{changed_object_id}"
288
+
289
+ # If a change already exists for this change_id, user, and object, update it instead of creating a new one.
290
+ # If the object was deleted then recreated with the same pk (don't do this), change the action to update.
291
+ if unique_object_change_id in change_context.deferred_object_changes:
292
+ related_changes = ObjectChange.objects.filter(
293
+ changed_object_type=changed_object_type,
294
+ changed_object_id=changed_object_id,
295
+ user=user,
296
+ request_id=change_context.change_id,
297
+ )
298
+
299
+ # Skip the database check when deferring object changes
300
+ if not change_context.defer_object_changes and related_changes.exists():
301
+ objectchange = instance.to_objectchange(action)
302
+ if objectchange is not None:
303
+ most_recent_change = related_changes.order_by("-time").first()
304
+ if most_recent_change.action == ObjectChangeActionChoices.ACTION_DELETE:
305
+ most_recent_change.action = ObjectChangeActionChoices.ACTION_UPDATE
306
+ most_recent_change.object_data = objectchange.object_data
307
+ most_recent_change.object_data_v2 = objectchange.object_data_v2
308
+ most_recent_change.save()
281
309
 
282
- else:
283
- change_context.deferred_object_changes[unique_object_change_id] = [
284
- {"action": action, "instance": instance, "user": user}
285
- ]
286
- if not change_context.defer_object_changes:
287
- objectchange = instance.to_objectchange(action)
288
- if objectchange is not None:
289
- objectchange.user = user
290
- objectchange.request_id = change_context.change_id
291
- objectchange.change_context = change_context.context
292
- objectchange.change_context_detail = change_context.context_detail[
293
- :CHANGELOG_MAX_CHANGE_CONTEXT_DETAIL
294
- ]
295
- objectchange.save()
296
-
297
- # restore field cache
298
- instance._state.fields_cache = original_cache
310
+ else:
311
+ change_context.deferred_object_changes[unique_object_change_id] = [
312
+ {"action": action, "instance": instance, "user": user}
313
+ ]
314
+ if not change_context.defer_object_changes:
315
+ objectchange = instance.to_objectchange(action)
316
+ if objectchange is not None:
317
+ objectchange.user = user
318
+ objectchange.request_id = change_context.change_id
319
+ objectchange.change_context = change_context.context
320
+ objectchange.change_context_detail = change_context.context_detail[
321
+ :CHANGELOG_MAX_CHANGE_CONTEXT_DETAIL
322
+ ]
323
+ objectchange.save()
324
+
325
+ # restore field cache
326
+ instance._state.fields_cache = original_cache
299
327
 
300
328
  # Increment metric counters
301
329
  if action == ObjectChangeActionChoices.ACTION_CREATE:
@@ -326,71 +354,73 @@ def _handle_deleted_object(sender, instance, **kwargs):
326
354
 
327
355
  # Record an ObjectChange if applicable
328
356
  if hasattr(instance, "to_objectchange"):
357
+ branch_name = _object_change_branch_name(instance)
329
358
  user = change_context.get_user(instance)
330
359
 
331
- # save a copy of this instance's field cache so it can be restored after serialization
332
- # to prevent unexpected behavior when chaining multiple signal handlers
333
- original_cache = instance._state.fields_cache.copy()
334
-
335
- changed_object_type = ContentType.objects.get_for_model(instance)
336
- changed_object_id = instance.id
337
-
338
- # Generate a unique identifier for this change to stash in the change context
339
- # This is used for deferred change logging and for looking up related changes without querying the database
340
- unique_object_change_id = f"{changed_object_type.pk}__{changed_object_id}__{user.pk}"
341
- save_new_objectchange = True
342
-
343
- # if a change already exists for this change_id, user, and object, update it instead of creating a new one
344
- # except in the case that the object was created and deleted in the same change_id
345
- # we don't want to create a delete change for an object that never existed
346
- if unique_object_change_id in change_context.deferred_object_changes:
347
- cached_related_change = change_context.deferred_object_changes[unique_object_change_id][-1]
348
- if cached_related_change["action"] != ObjectChangeActionChoices.ACTION_CREATE:
349
- cached_related_change["action"] = ObjectChangeActionChoices.ACTION_DELETE
350
- save_new_objectchange = False
351
-
352
- related_changes = ObjectChange.objects.filter(
353
- changed_object_type=changed_object_type,
354
- changed_object_id=changed_object_id,
355
- user=user,
356
- request_id=change_context.change_id,
357
- )
360
+ with BranchContext(branch_name=branch_name, user=user, autocommit=False):
361
+ # save a copy of this instance's field cache so it can be restored after serialization
362
+ # to prevent unexpected behavior when chaining multiple signal handlers
363
+ original_cache = instance._state.fields_cache.copy()
358
364
 
359
- # Skip the database check when deferring object changes
360
- if not change_context.defer_object_changes and related_changes.exists():
361
- objectchange = instance.to_objectchange(ObjectChangeActionChoices.ACTION_DELETE)
362
- if objectchange is not None:
363
- most_recent_change = related_changes.order_by("-time").first()
364
- if most_recent_change.action != ObjectChangeActionChoices.ACTION_CREATE:
365
- most_recent_change.action = ObjectChangeActionChoices.ACTION_DELETE
366
- most_recent_change.object_data = objectchange.object_data
367
- most_recent_change.object_data_v2 = objectchange.object_data_v2
368
- most_recent_change.save()
369
- save_new_objectchange = False
370
-
371
- if save_new_objectchange:
372
- change_context.deferred_object_changes.setdefault(unique_object_change_id, []).append(
373
- {
374
- "action": ObjectChangeActionChoices.ACTION_DELETE,
375
- "instance": instance,
376
- "user": user,
377
- "changed_object_id": changed_object_id,
378
- "changed_object_type": changed_object_type,
379
- }
380
- )
381
- if not change_context.defer_object_changes:
382
- objectchange = instance.to_objectchange(ObjectChangeActionChoices.ACTION_DELETE)
383
- if objectchange is not None:
384
- objectchange.user = user
385
- objectchange.request_id = change_context.change_id
386
- objectchange.change_context = change_context.context
387
- objectchange.change_context_detail = change_context.context_detail[
388
- :CHANGELOG_MAX_CHANGE_CONTEXT_DETAIL
389
- ]
390
- objectchange.save()
391
-
392
- # restore field cache
393
- instance._state.fields_cache = original_cache
365
+ changed_object_type = ContentType.objects.get_for_model(instance)
366
+ changed_object_id = instance.id
367
+
368
+ # Generate a unique identifier for this change to stash in the change context
369
+ # This is used for deferred change logging and for looking up related changes without querying the database
370
+ unique_object_change_id = f"{changed_object_type.pk}__{changed_object_id}__{user.pk}"
371
+ save_new_objectchange = True
372
+
373
+ # if a change already exists for this change_id, user, and object, update it instead of creating a new one
374
+ # except in the case that the object was created and deleted in the same change_id
375
+ # we don't want to create a delete change for an object that never existed
376
+ if unique_object_change_id in change_context.deferred_object_changes:
377
+ cached_related_change = change_context.deferred_object_changes[unique_object_change_id][-1]
378
+ if cached_related_change["action"] != ObjectChangeActionChoices.ACTION_CREATE:
379
+ cached_related_change["action"] = ObjectChangeActionChoices.ACTION_DELETE
380
+ save_new_objectchange = False
381
+
382
+ related_changes = ObjectChange.objects.filter(
383
+ changed_object_type=changed_object_type,
384
+ changed_object_id=changed_object_id,
385
+ user=user,
386
+ request_id=change_context.change_id,
387
+ )
388
+
389
+ # Skip the database check when deferring object changes
390
+ if not change_context.defer_object_changes and related_changes.exists():
391
+ objectchange = instance.to_objectchange(ObjectChangeActionChoices.ACTION_DELETE)
392
+ if objectchange is not None:
393
+ most_recent_change = related_changes.order_by("-time").first()
394
+ if most_recent_change.action != ObjectChangeActionChoices.ACTION_CREATE:
395
+ most_recent_change.action = ObjectChangeActionChoices.ACTION_DELETE
396
+ most_recent_change.object_data = objectchange.object_data
397
+ most_recent_change.object_data_v2 = objectchange.object_data_v2
398
+ most_recent_change.save()
399
+ save_new_objectchange = False
400
+
401
+ if save_new_objectchange:
402
+ change_context.deferred_object_changes.setdefault(unique_object_change_id, []).append(
403
+ {
404
+ "action": ObjectChangeActionChoices.ACTION_DELETE,
405
+ "instance": instance,
406
+ "user": user,
407
+ "changed_object_id": changed_object_id,
408
+ "changed_object_type": changed_object_type,
409
+ }
410
+ )
411
+ if not change_context.defer_object_changes:
412
+ objectchange = instance.to_objectchange(ObjectChangeActionChoices.ACTION_DELETE)
413
+ if objectchange is not None:
414
+ objectchange.user = user
415
+ objectchange.request_id = change_context.change_id
416
+ objectchange.change_context = change_context.context
417
+ objectchange.change_context_detail = change_context.context_detail[
418
+ :CHANGELOG_MAX_CHANGE_CONTEXT_DETAIL
419
+ ]
420
+ objectchange.save()
421
+
422
+ # restore field cache
423
+ instance._state.fields_cache = original_cache
394
424
 
395
425
  # Increment metric counters
396
426
  model_deletes.labels(instance._meta.model_name).inc()