nautobot 3.0.4__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 (363) hide show
  1. nautobot/core/tables.py +28 -17
  2. nautobot/core/templates/graphene/graphiql.html +3 -5
  3. nautobot/core/templates/inc/javascript.html +5 -10
  4. nautobot/core/templates/inc/media.html +5 -4
  5. nautobot/core/templates/inc/media_failure.html +73 -0
  6. nautobot/core/templates/media_failure.html +1 -0
  7. nautobot/core/tests/test_templatetags_helpers.py +9 -9
  8. nautobot/core/ui/object_detail.py +1 -0
  9. nautobot/dcim/tables/devices.py +4 -4
  10. nautobot/dcim/tables/template_code.py +8 -4
  11. nautobot/dcim/tests/test_tables.py +5 -6
  12. nautobot/dcim/views.py +6 -7
  13. nautobot/extras/tables.py +3 -3
  14. nautobot/extras/templates/extras/inc/jobresult_js.html +1 -2
  15. nautobot/extras/templates/extras/scheduledjob.html +3 -1
  16. nautobot/extras/tests/test_customfields.py +75 -9
  17. nautobot/ipam/filters.py +58 -3
  18. nautobot/ipam/tables.py +8 -4
  19. nautobot/ipam/tests/test_filters.py +55 -0
  20. nautobot/project-static/dist/css/nautobot.css +1 -1
  21. nautobot/project-static/dist/css/nautobot.css.map +1 -1
  22. nautobot/project-static/docs/404.html +64 -8
  23. nautobot/project-static/docs/apps/index.html +64 -8
  24. nautobot/project-static/docs/apps/nautobot-apps.html +64 -8
  25. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +64 -8
  26. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +64 -8
  27. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +64 -8
  28. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +64 -8
  29. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +64 -8
  30. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +64 -8
  31. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +64 -8
  32. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +64 -8
  33. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +64 -8
  34. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +64 -8
  35. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +64 -8
  36. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +64 -8
  37. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +64 -8
  38. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +64 -8
  39. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +64 -8
  40. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +64 -8
  41. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +64 -8
  42. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +64 -8
  43. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +64 -8
  44. nautobot/project-static/docs/code-reference/nautobot/apps/templatetags.html +64 -8
  45. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +64 -8
  46. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +64 -8
  47. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +64 -8
  48. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +64 -8
  49. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +64 -8
  50. nautobot/project-static/docs/development/apps/api/configuration-view.html +64 -8
  51. nautobot/project-static/docs/development/apps/api/database-backend-config.html +64 -8
  52. nautobot/project-static/docs/development/apps/api/models/django-admin.html +67 -11
  53. nautobot/project-static/docs/development/apps/api/models/global-search.html +64 -8
  54. nautobot/project-static/docs/development/apps/api/models/graphql.html +64 -8
  55. nautobot/project-static/docs/development/apps/api/models/index.html +64 -8
  56. nautobot/project-static/docs/development/apps/api/models/queryset.html +13440 -0
  57. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +64 -8
  58. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +64 -8
  59. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +64 -8
  60. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +64 -8
  61. nautobot/project-static/docs/development/apps/api/platform-features/index.html +64 -8
  62. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +64 -8
  63. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +64 -8
  64. nautobot/project-static/docs/development/apps/api/platform-features/prepopulating-data.html +64 -8
  65. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +64 -8
  66. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +64 -8
  67. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +64 -8
  68. nautobot/project-static/docs/development/apps/api/prometheus.html +64 -8
  69. nautobot/project-static/docs/development/apps/api/setup.html +64 -8
  70. nautobot/project-static/docs/development/apps/api/testing.html +64 -8
  71. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +64 -8
  72. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +64 -8
  73. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +64 -8
  74. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +64 -8
  75. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +64 -8
  76. nautobot/project-static/docs/development/apps/api/views/base-template.html +64 -8
  77. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +64 -8
  78. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +64 -8
  79. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +64 -8
  80. nautobot/project-static/docs/development/apps/api/views/index.html +67 -11
  81. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +64 -8
  82. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +64 -8
  83. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +64 -8
  84. nautobot/project-static/docs/development/apps/api/views/notes.html +64 -8
  85. nautobot/project-static/docs/development/apps/api/views/rest-api.html +64 -8
  86. nautobot/project-static/docs/development/apps/api/views/urls.html +64 -8
  87. nautobot/project-static/docs/development/apps/index.html +64 -8
  88. nautobot/project-static/docs/development/apps/migration/code-updates.html +64 -8
  89. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +64 -8
  90. nautobot/project-static/docs/development/apps/migration/from-v1.html +64 -8
  91. nautobot/project-static/docs/development/apps/migration/from-v2/migrating-v2-to-v3.html +64 -8
  92. nautobot/project-static/docs/development/apps/migration/from-v2/new-nautobot-custom-ui-apis.html +64 -8
  93. nautobot/project-static/docs/development/apps/migration/from-v2/overview.html +68 -8
  94. nautobot/project-static/docs/development/apps/migration/from-v2/upgrading-from-bootstrap-v3-to-v5.html +64 -8
  95. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +64 -8
  96. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +64 -8
  97. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +64 -8
  98. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +64 -8
  99. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +64 -8
  100. nautobot/project-static/docs/development/apps/migration/ui-component-framework/breadcrumbs-titles.html +64 -8
  101. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +64 -8
  102. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +64 -8
  103. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +64 -8
  104. nautobot/project-static/docs/development/apps/porting-from-netbox.html +64 -8
  105. nautobot/project-static/docs/development/core/application-registry.html +64 -8
  106. nautobot/project-static/docs/development/core/best-practices.html +64 -8
  107. nautobot/project-static/docs/development/core/caching.html +64 -8
  108. nautobot/project-static/docs/development/core/controllers.html +64 -8
  109. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +64 -8
  110. nautobot/project-static/docs/development/core/docs-media-standards.html +64 -8
  111. nautobot/project-static/docs/development/core/generic-views.html +64 -8
  112. nautobot/project-static/docs/development/core/getting-started.html +64 -8
  113. nautobot/project-static/docs/development/core/homepage.html +64 -8
  114. nautobot/project-static/docs/development/core/index.html +64 -8
  115. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +64 -8
  116. nautobot/project-static/docs/development/core/model-checklist.html +64 -8
  117. nautobot/project-static/docs/development/core/model-features.html +64 -8
  118. nautobot/project-static/docs/development/core/natural-keys.html +64 -8
  119. nautobot/project-static/docs/development/core/navigation-menu.html +64 -8
  120. nautobot/project-static/docs/development/core/release-checklist.html +64 -8
  121. nautobot/project-static/docs/development/core/role-internals.html +64 -8
  122. nautobot/project-static/docs/development/core/settings.html +64 -8
  123. nautobot/project-static/docs/development/core/style-guide.html +64 -8
  124. nautobot/project-static/docs/development/core/templates.html +64 -8
  125. nautobot/project-static/docs/development/core/testing.html +64 -8
  126. nautobot/project-static/docs/development/core/ui-best-practices.html +64 -8
  127. nautobot/project-static/docs/development/core/ui-component-framework.html +64 -8
  128. nautobot/project-static/docs/development/core/user-preferences.html +64 -8
  129. nautobot/project-static/docs/development/index.html +64 -8
  130. nautobot/project-static/docs/development/jobs/getting-started.html +64 -8
  131. nautobot/project-static/docs/development/jobs/index.html +64 -8
  132. nautobot/project-static/docs/development/jobs/installation.html +64 -8
  133. nautobot/project-static/docs/development/jobs/job-extensions.html +64 -8
  134. nautobot/project-static/docs/development/jobs/job-logging.html +64 -8
  135. nautobot/project-static/docs/development/jobs/job-patterns.html +64 -8
  136. nautobot/project-static/docs/development/jobs/job-structure.html +64 -8
  137. nautobot/project-static/docs/development/jobs/migration/from-v1.html +64 -8
  138. nautobot/project-static/docs/development/jobs/testing.html +64 -8
  139. nautobot/project-static/docs/index.html +64 -8
  140. nautobot/project-static/docs/overview/application_stack.html +64 -8
  141. nautobot/project-static/docs/overview/design_philosophy.html +64 -8
  142. nautobot/project-static/docs/release-notes/index.html +64 -8
  143. nautobot/project-static/docs/release-notes/version-1.0.html +64 -8
  144. nautobot/project-static/docs/release-notes/version-1.1.html +64 -8
  145. nautobot/project-static/docs/release-notes/version-1.2.html +65 -9
  146. nautobot/project-static/docs/release-notes/version-1.3.html +64 -8
  147. nautobot/project-static/docs/release-notes/version-1.4.html +64 -8
  148. nautobot/project-static/docs/release-notes/version-1.5.html +64 -8
  149. nautobot/project-static/docs/release-notes/version-1.6.html +64 -8
  150. nautobot/project-static/docs/release-notes/version-2.0.html +64 -8
  151. nautobot/project-static/docs/release-notes/version-2.1.html +64 -8
  152. nautobot/project-static/docs/release-notes/version-2.2.html +64 -8
  153. nautobot/project-static/docs/release-notes/version-2.3.html +64 -8
  154. nautobot/project-static/docs/release-notes/version-2.4.html +584 -8
  155. nautobot/project-static/docs/release-notes/version-3.0.html +232 -8
  156. nautobot/project-static/docs/search/search_index.json +1 -1
  157. nautobot/project-static/docs/sitemap.xml +337 -329
  158. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  159. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +64 -8
  160. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +64 -8
  161. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +64 -8
  162. nautobot/project-static/docs/user-guide/administration/configuration/index.html +64 -8
  163. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +64 -8
  164. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +64 -8
  165. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +64 -8
  166. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +64 -8
  167. nautobot/project-static/docs/user-guide/administration/guides/docker.html +64 -8
  168. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +64 -8
  169. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +64 -8
  170. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +64 -8
  171. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +64 -8
  172. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +64 -8
  173. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +64 -8
  174. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +64 -8
  175. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +64 -8
  176. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +64 -8
  177. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +72 -9
  178. nautobot/project-static/docs/user-guide/administration/installation/index.html +64 -8
  179. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +64 -8
  180. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +64 -8
  181. nautobot/project-static/docs/user-guide/administration/installation/services.html +64 -8
  182. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +65 -9
  183. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +64 -8
  184. nautobot/project-static/docs/user-guide/administration/security/index.html +64 -8
  185. nautobot/project-static/docs/user-guide/administration/security/notices.html +64 -8
  186. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +64 -8
  187. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +64 -8
  188. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +67 -11
  189. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +64 -8
  190. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +64 -8
  191. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +64 -8
  192. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +64 -8
  193. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +64 -8
  194. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +64 -8
  195. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +64 -8
  196. nautobot/project-static/docs/user-guide/administration/upgrading/from-v2/index.html +68 -8
  197. nautobot/project-static/docs/user-guide/administration/upgrading/postgresql.html +13391 -0
  198. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +125 -15
  199. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +64 -8
  200. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +64 -8
  201. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +64 -8
  202. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +64 -8
  203. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +64 -8
  204. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +64 -8
  205. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +64 -8
  206. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +64 -8
  207. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +64 -8
  208. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +64 -8
  209. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +64 -8
  210. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +64 -8
  211. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +64 -8
  212. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +64 -8
  213. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +64 -8
  214. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +64 -8
  215. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +64 -8
  216. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +64 -8
  217. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +64 -8
  218. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +64 -8
  219. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +64 -8
  220. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +64 -8
  221. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +64 -8
  222. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +64 -8
  223. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +64 -8
  224. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +64 -8
  225. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +64 -8
  226. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +64 -8
  227. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +64 -8
  228. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +64 -8
  229. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +64 -8
  230. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +64 -8
  231. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +64 -8
  232. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +64 -8
  233. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +64 -8
  234. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +64 -8
  235. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +64 -8
  236. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +64 -8
  237. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +64 -8
  238. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +64 -8
  239. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +64 -8
  240. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +64 -8
  241. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +64 -8
  242. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +64 -8
  243. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +64 -8
  244. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +64 -8
  245. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +64 -8
  246. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +64 -8
  247. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +64 -8
  248. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +64 -8
  249. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +64 -8
  250. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +64 -8
  251. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +64 -8
  252. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +64 -8
  253. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +64 -8
  254. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +64 -8
  255. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +64 -8
  256. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +64 -8
  257. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +64 -8
  258. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +64 -8
  259. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +64 -8
  260. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +64 -8
  261. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +64 -8
  262. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +64 -8
  263. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +64 -8
  264. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +64 -8
  265. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +64 -8
  266. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +64 -8
  267. nautobot/project-static/docs/user-guide/core-data-model/load-balancers/certificateprofile.html +64 -8
  268. nautobot/project-static/docs/user-guide/core-data-model/load-balancers/healthcheckmonitor.html +64 -8
  269. nautobot/project-static/docs/user-guide/core-data-model/load-balancers/index.html +64 -8
  270. nautobot/project-static/docs/user-guide/core-data-model/load-balancers/loadbalancerpool.html +64 -8
  271. nautobot/project-static/docs/user-guide/core-data-model/load-balancers/loadbalancerpoolmember.html +64 -8
  272. nautobot/project-static/docs/user-guide/core-data-model/load-balancers/virtualserver.html +64 -8
  273. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +64 -8
  274. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +64 -8
  275. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +64 -8
  276. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +64 -8
  277. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +64 -8
  278. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +64 -8
  279. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +64 -8
  280. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +64 -8
  281. nautobot/project-static/docs/user-guide/core-data-model/vpn/index.html +64 -8
  282. nautobot/project-static/docs/user-guide/core-data-model/vpn/vpn.html +64 -8
  283. nautobot/project-static/docs/user-guide/core-data-model/vpn/vpnphase1policy.html +64 -8
  284. nautobot/project-static/docs/user-guide/core-data-model/vpn/vpnphase2policy.html +64 -8
  285. nautobot/project-static/docs/user-guide/core-data-model/vpn/vpnprofile.html +64 -8
  286. nautobot/project-static/docs/user-guide/core-data-model/vpn/vpntunnel.html +64 -8
  287. nautobot/project-static/docs/user-guide/core-data-model/vpn/vpntunnelendpoint.html +64 -8
  288. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +64 -8
  289. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +64 -8
  290. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +64 -8
  291. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +64 -8
  292. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +64 -8
  293. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +64 -8
  294. nautobot/project-static/docs/user-guide/feature-guides/data-compliance.html +64 -8
  295. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +64 -8
  296. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +64 -8
  297. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +64 -8
  298. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +64 -8
  299. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +64 -8
  300. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +64 -8
  301. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +64 -8
  302. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +64 -8
  303. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +64 -8
  304. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +64 -8
  305. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +64 -8
  306. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +64 -8
  307. nautobot/project-static/docs/user-guide/feature-guides/load-balancers.html +64 -8
  308. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +64 -8
  309. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +64 -8
  310. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +64 -8
  311. nautobot/project-static/docs/user-guide/index.html +64 -8
  312. nautobot/project-static/docs/user-guide/platform-functionality/approval-workflow.html +64 -8
  313. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +64 -8
  314. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +64 -8
  315. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +64 -8
  316. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +64 -8
  317. nautobot/project-static/docs/user-guide/platform-functionality/data-validation.html +64 -8
  318. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +64 -8
  319. nautobot/project-static/docs/user-guide/platform-functionality/echarts.html +64 -8
  320. nautobot/project-static/docs/user-guide/platform-functionality/events.html +64 -8
  321. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +64 -8
  322. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +64 -8
  323. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +64 -8
  324. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +64 -8
  325. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +64 -8
  326. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +64 -8
  327. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +64 -8
  328. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +64 -8
  329. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +64 -8
  330. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +64 -8
  331. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +64 -8
  332. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +64 -8
  333. nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +64 -8
  334. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +64 -8
  335. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +64 -8
  336. nautobot/project-static/docs/user-guide/platform-functionality/note.html +64 -8
  337. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +64 -8
  338. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +64 -8
  339. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +64 -8
  340. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +64 -8
  341. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +116 -34
  342. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +64 -8
  343. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +64 -8
  344. nautobot/project-static/docs/user-guide/platform-functionality/role.html +64 -8
  345. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +64 -8
  346. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +64 -8
  347. nautobot/project-static/docs/user-guide/platform-functionality/status.html +64 -8
  348. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +64 -8
  349. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +64 -8
  350. nautobot/project-static/docs/user-guide/platform-functionality/user-interface/configurablecolumns.html +64 -8
  351. nautobot/project-static/docs/user-guide/platform-functionality/user-interface/savedview.html +64 -8
  352. nautobot/project-static/docs/user-guide/platform-functionality/user-interface/search.html +64 -8
  353. nautobot/project-static/docs/user-guide/platform-functionality/users/groups.html +64 -8
  354. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +64 -8
  355. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +64 -8
  356. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +64 -8
  357. nautobot/ui/src/scss/nautobot.scss +2 -1
  358. {nautobot-3.0.4.dist-info → nautobot-3.0.5.dist-info}/METADATA +1 -1
  359. {nautobot-3.0.4.dist-info → nautobot-3.0.5.dist-info}/RECORD +363 -360
  360. {nautobot-3.0.4.dist-info → nautobot-3.0.5.dist-info}/LICENSE.txt +0 -0
  361. {nautobot-3.0.4.dist-info → nautobot-3.0.5.dist-info}/NOTICE +0 -0
  362. {nautobot-3.0.4.dist-info → nautobot-3.0.5.dist-info}/WHEEL +0 -0
  363. {nautobot-3.0.4.dist-info → nautobot-3.0.5.dist-info}/entry_points.txt +0 -0
nautobot/core/tables.py CHANGED
@@ -9,7 +9,7 @@ from django.db.models import Prefetch, QuerySet
9
9
  from django.db.models.fields.related import ForeignKey, RelatedField
10
10
  from django.db.models.fields.reverse_related import ManyToOneRel
11
11
  from django.urls import reverse
12
- from django.utils.html import escape, format_html, format_html_join
12
+ from django.utils.html import format_html, format_html_join
13
13
  from django.utils.http import urlencode
14
14
  from django.utils.safestring import mark_safe
15
15
  from django.utils.text import Truncator
@@ -36,6 +36,7 @@ class BaseTable(django_tables2.Table):
36
36
  attrs = {
37
37
  "class": "table table-hover nb-table-headings",
38
38
  }
39
+ default = helpers.HTML_NONE
39
40
 
40
41
  def __init__(
41
42
  self,
@@ -579,7 +580,13 @@ class ColoredLabelColumn(django_tables2.TemplateColumn):
579
580
 
580
581
  template_code = """
581
582
  {% load helpers %}
582
- {% if value %}<span class="badge" style="color: {{ value.color|fgcolor }}; background-color: #{{ value.color }}">{{ value }}</span>{% else %}&mdash;{% endif %}
583
+ {% if value %}
584
+ <span class="badge" style="color: {{ value.color|fgcolor }}; background-color: #{{ value.color }}">
585
+ {{ value }}
586
+ </span>
587
+ {% else %}
588
+ <span class="text-secondary">&mdash;</span>
589
+ {% endif %}
583
590
  """
584
591
 
585
592
  def __init__(self, *args, **kwargs):
@@ -757,29 +764,33 @@ class CustomFieldColumn(django_tables2.Column):
757
764
  Display custom fields in the appropriate format.
758
765
  """
759
766
 
760
- # Add [] to empty_values so when there is no choice populated for multiselect_cf i.e. [], "—" is returned automatically.
761
- empty_values = (None, "", [])
762
-
763
767
  def __init__(self, customfield, *args, **kwargs):
764
768
  self.customfield = customfield
765
769
  kwargs["accessor"] = Accessor(f"_custom_field_data__{customfield.key}")
766
770
  kwargs["verbose_name"] = customfield.label
771
+ if self.customfield.type == choices.CustomFieldTypeChoices.TYPE_MULTISELECT:
772
+ # Add [] to empty_values so when there is no choice populated i.e. [], "—" is returned automatically.
773
+ kwargs.setdefault("empty_values", (None, "", []))
767
774
 
768
775
  super().__init__(*args, **kwargs)
769
776
 
770
777
  def render(self, *, record, bound_column, value): # pylint: disable=arguments-differ # tables2 varies its kwargs
771
- if self.customfield.type == choices.CustomFieldTypeChoices.TYPE_BOOLEAN:
772
- template = helpers.render_boolean(value)
773
- elif self.customfield.type == choices.CustomFieldTypeChoices.TYPE_MULTISELECT:
774
- template = format_html_join(" ", '<span class="badge bg-secondary">{}</span>', ((v,) for v in value))
775
- elif self.customfield.type == choices.CustomFieldTypeChoices.TYPE_SELECT:
776
- template = format_html('<span class="badge bg-secondary">{}</span>', value)
777
- elif self.customfield.type == choices.CustomFieldTypeChoices.TYPE_URL:
778
- template = format_html('<a href="{}">{}</a>', value, value)
779
- else:
780
- template = escape(value)
778
+ # TODO: this logic could be unified with _ObjectCustomFieldsPanel.render_value
779
+ if self.customfield.type == choices.CustomFieldTypeChoices.TYPE_BOOLEAN and value is not None:
780
+ value = helpers.render_boolean(value)
781
+ elif self.customfield.type == choices.CustomFieldTypeChoices.TYPE_JSON and value is not None:
782
+ value = helpers.render_json(value, pretty_print=True)
783
+ elif self.customfield.type == choices.CustomFieldTypeChoices.TYPE_MARKDOWN and value is not None:
784
+ value = helpers.render_markdown(value)
785
+ elif self.customfield.type == choices.CustomFieldTypeChoices.TYPE_MULTISELECT and value:
786
+ value = format_html_join(" ", '<span class="badge bg-secondary">{}</span>', ((v,) for v in value))
787
+ elif self.customfield.type == choices.CustomFieldTypeChoices.TYPE_SELECT and value is not None:
788
+ value = format_html('<span class="badge bg-secondary">{}</span>', value)
789
+ elif self.customfield.type == choices.CustomFieldTypeChoices.TYPE_URL and value:
790
+ value = format_html('<a href="{}">{}</a>', value, value)
791
+ # else (TEXT, INTEGER, DATE) or None value -- no need to do special rendering
781
792
 
782
- return template
793
+ return value
783
794
 
784
795
 
785
796
  class RelationshipColumn(django_tables2.Column):
@@ -811,7 +822,7 @@ class RelationshipColumn(django_tables2.Column):
811
822
  # Handle Symmetric Relationships
812
823
  # List `value` could be empty here [] after the filtering from above
813
824
  if len(value) < 1:
814
- return "—"
825
+ return helpers.HTML_NONE
815
826
 
816
827
  v = value[0]
817
828
  peer = v.get_peer(record)
@@ -81,11 +81,9 @@ add "&raw" to the end of the URL within a browser.
81
81
  <!-- As Nautobot may be run without internet access, we source these files locally rather than from an online CDN -->
82
82
  <link rel="stylesheet"
83
83
  href="{% static 'dist/css/graphql-libraries.css' %}"
84
- onerror="window.location='{% url 'media_failure' %}?filename=dist/css/graphql-libraries.css'">
85
- <script src="{% static 'dist/js/graphql-libraries.js' %}"
86
- onerror="window.location='{% url 'media_failure' %}?filename=dist/js/graphiql-libraries.js'"></script>
87
- <script src="{% static 'dist/js/nautobot-graphiql.js' %}"
88
- onerror="window.location='{% url 'media_failure' %}?filename=dist/js/nautobot-graphiql.js'"></script>
84
+ onerror="nb.media.handleFailure(this)">
85
+ <script src="{% static 'dist/js/graphql-libraries.js' %}" onerror="nb.media.handleFailure(this)"></script>
86
+ <script src="{% static 'dist/js/nautobot-graphiql.js' %}" onerror="nb.media.handleFailure(this)"></script>
89
87
  </head>
90
88
  <body>
91
89
  <!-- Nautobot page contents -->
@@ -2,16 +2,11 @@
2
2
  {% load plugins %}
3
3
  {% load static %}
4
4
 
5
- <script src="{% static 'dist/js/nautobot.js' %}"
6
- onerror="window.location='{% url 'media_failure' %}?filename=dist/js/nautobot.js'"></script>
7
- <script src="{% static 'dist/js/libraries.js' %}"
8
- onerror="window.location='{% url 'media_failure' %}?filename=dist/js/libraries.js'"></script>
9
- <script src="{% versioned_static 'js/forms.js' %}"
10
- onerror="window.location='{% url 'media_failure' %}?filename=js/forms.js'"></script>
11
- <script src="{% versioned_static 'js/table_sorting_indicator.js' %}"
12
- onerror="window.location='{% url 'media_failure' %}?filename=js/table_sorting_indicator.js'"></script>
13
- <script src="{% versioned_static 'js/dropdown.js' %}"
14
- onerror="window.location='{% url 'media_failure' %}?filename=js/dropdown.js'"></script>
5
+ <script src="{% static 'dist/js/nautobot.js' %}" onerror="nb.media.handleFailure(this)"></script>
6
+ <script src="{% static 'dist/js/libraries.js' %}" onerror="nb.media.handleFailure(this)"></script>
7
+ <script src="{% versioned_static 'js/forms.js' %}" onerror="nb.media.handleFailure(this)"></script>
8
+ <script src="{% versioned_static 'js/table_sorting_indicator.js' %}" onerror="nb.media.handleFailure(this)"></script>
9
+ <script src="{% versioned_static 'js/dropdown.js' %}" onerror="nb.media.handleFailure(this)"></script>
15
10
  <script type="text/javascript">
16
11
  var nautobot_static_url = "{% static '' %}";
17
12
  var nautobot_api_path = "{% url 'api-root' %}";
@@ -1,25 +1,26 @@
1
1
  {% load static %}
2
2
  {% load helpers %}
3
3
  {% load plugins %}
4
+ {% include 'inc/media_failure.html' %}
4
5
  <link rel="stylesheet" id="template-theme"
5
6
  href="{% url 'template_css' %}">
6
7
  <link rel="stylesheet"
7
8
  href="{% static 'dist/css/nautobot.css' %}"
8
- onerror="window.location='{% url 'media_failure' %}?filename=dist/css/nautobot.css'">
9
+ onerror="nb.media.handleFailure(this)">
9
10
  <link rel="stylesheet"
10
11
  href="{% static 'dist/css/materialdesignicons.css' %}"
11
- onerror="window.location='{% url 'media_failure' %}?filename=dist/css/materialdesignicons.css'">
12
+ onerror="nb.media.handleFailure(this)">
12
13
  {% with cookie_theme=request.COOKIES|get_item:'theme' %}
13
14
  {% if not cookie_theme or cookie_theme == 'light' %}
14
15
  <link rel="stylesheet"
15
16
  href="{% static 'dist/css/github.min.css' %}"
16
- onerror="window.location='{% url 'media_failure' %}?filename=dist/css/github.min.css'"
17
+ onerror="nb.media.handleFailure(this)"
17
18
  {% if not cookie_theme %}media="not (prefers-color-scheme: dark)"{% endif %}>
18
19
  {% endif %}
19
20
  {% if not cookie_theme or cookie_theme == 'dark' %}
20
21
  <link rel="stylesheet"
21
22
  href="{% static 'dist/css/github-dark.min.css' %}"
22
- onerror="window.location='{% url 'media_failure' %}?filename=dist/css/github-dark.min.css'"
23
+ onerror="nb.media.handleFailure(this)"
23
24
  {% if not cookie_theme %}media="(prefers-color-scheme: dark)"{% endif %}>
24
25
  {% endif %}
25
26
  {% endwith %}
@@ -0,0 +1,73 @@
1
+ {% load static %}
2
+
3
+ <script>
4
+ (function initMediaFailure() {
5
+ // Create React-style `ref` reference to the media failure alert element.
6
+ const failureAlertRef = { current: null };
7
+
8
+ // Store all media failures inside `Map` containing `Set` values to ensure uniqueness of all reported failures.
9
+ const failures = new Map();
10
+
11
+ function handleFailure(element) {
12
+ const url = element.href || element.src || '';
13
+
14
+ if (url) {
15
+ // Update `failures` with a proper entry corresponding to the current media failure and update media failure alert.
16
+ const elements = failures.get(url) || new Set();
17
+ elements.add(element);
18
+ failures.set(url, elements);
19
+ updateFailureAlert();
20
+ }
21
+ }
22
+
23
+ function updateFailureAlert() {
24
+ const count = Array.from(failures.values()).reduce((count, elements) => count + elements.size, 0);
25
+
26
+ const innerHTML = `
27
+ <div style="margin: auto; max-width: 50rem;">
28
+ <h2 style="margin-top: 0; text-align: center;">Static Media Failure</h2>
29
+ <p><strong>${count}</strong> static media file${count > 1 ? 's' : ''} failed to load.</p>
30
+ <p style="margin-bottom: 0;">
31
+ Check whether
32
+ <code><strong>nautobot-server collectstatic</strong></code>
33
+ was run during the most recent upgrade and that the HTTP service (e.g. NGINX) is configured to serve static files.
34
+ Refer to
35
+ <a href="https://docs.nautobot.com/projects/core/en/v{{ settings.VERSION }}/installation/http-server/#static-media-failure" target="_blank">
36
+ the installation documentation
37
+ </a>
38
+ for further guidance.
39
+ </p>
40
+ </div>
41
+ `;
42
+
43
+ // If media failure alert already exists, just update its content and return early.
44
+ if (failureAlertRef.current) {
45
+ failureAlertRef.current.innerHTML = innerHTML;
46
+ return;
47
+ }
48
+
49
+ // If media failure alert does not exist yet, create one.
50
+ const alert = document.createElement('div');
51
+ alert.classList.add('alert', 'alert-danger', 'media-failure-banner');
52
+ alert.setAttribute('role', 'alert');
53
+ alert.innerHTML = innerHTML;
54
+
55
+ // Copy and hardcode Bootstrap `"alert alert-danger"` styles here to make the alert core styles independent of other CSS.
56
+ alert.style.setProperty('background-color', 'var(--bs-alert-bg, var(--bs-danger-bg-subtle, #fce8e8))');
57
+ alert.style.setProperty('border', 'var(--bs-alert-border, var(--bs-border-width, 0.0625rem) solid var(--bs-alert-border-color, var(--bs-danger-border-subtle, #f9d2d2)))');
58
+ alert.style.setProperty('border-radius', '0');
59
+ alert.style.setProperty('color', 'var(--bs-alert-color, var(--bs-danger-text-emphasis, #e01f1f))');
60
+ alert.style.setProperty('grid-area', 'banner-global-area');
61
+ alert.style.setProperty('padding', 'var(--bs-alert-padding-y, 1rem) 1.25rem');
62
+ alert.style.setProperty('position', 'relative');
63
+
64
+ // Update React-style `ref` to simplify later alert updates if required and place the alert as the first child of document body.
65
+ failureAlertRef.current = alert;
66
+ document.body.prepend(alert);
67
+ }
68
+
69
+ // Export `media` "API" to `window` global object to enable accessing it from other scripts.
70
+ window.nb = window.nb || {};
71
+ window.nb.media = { failures, handleFailure }
72
+ })();
73
+ </script>
@@ -1,3 +1,4 @@
1
+ {% comment %}4.0 TODO: remove this template, which only exists for backward compatibility with 3.0 and earlier{% endcomment %}
1
2
  {% load static %}
2
3
  <!DOCTYPE html>
3
4
  <html lang="en">
@@ -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)
@@ -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,
@@ -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
 
@@ -1505,7 +1505,7 @@ class ControllerTable(StatusTableMixin, RoleTableMixin, BaseTable):
1505
1505
  def render_capabilities(self, value):
1506
1506
  """Render capabilities."""
1507
1507
  if not value:
1508
- return format_html("&mdash;")
1508
+ return HTML_NONE
1509
1509
  return format_html_join(" ", '<span class="badge bg-secondary">{}</span>', ((v,) for v in value))
1510
1510
 
1511
1511
 
@@ -1569,7 +1569,7 @@ class ControllerManagedDeviceGroupTable(BaseTable):
1569
1569
  def render_capabilities(self, value):
1570
1570
  """Render capabilities."""
1571
1571
  if not value:
1572
- return format_html("&mdash;")
1572
+ return HTML_NONE
1573
1573
  return format_html_join(" ", '<span class="badge bg-secondary">{}</span>', ((v,) for v in value))
1574
1574
 
1575
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
 
@@ -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
 
nautobot/extras/tables.py CHANGED
@@ -374,11 +374,11 @@ class ApprovalWorkflowStageTable(BaseTable):
374
374
  actions_needed = tables.TemplateColumn(
375
375
  template_code="""
376
376
  {% if record.remaining_approvals == 1 %}
377
- {{ record.remaining_approvals }} more approval needed
377
+ {{ record.remaining_approvals }} more approval needed
378
378
  {% elif record.remaining_approvals == 0 %}
379
- <span class="text-secondary">&mdash;</span>
379
+ <span class="text-secondary">&mdash;</span>
380
380
  {% else %}
381
- {{ record.remaining_approvals }} more approvals needed
381
+ {{ record.remaining_approvals }} more approvals needed
382
382
  {% endif %}
383
383
  """,
384
384
  orderable=False,
@@ -10,5 +10,4 @@
10
10
  var job_result_id = "{{ result.pk }}";
11
11
 
12
12
  </script>
13
- <script src="{% versioned_static 'js/job_result.js' %}"
14
- onerror="window.location='{% url 'media_failure' %}?filename=js/job_result.js'"></script>
13
+ <script src="{% versioned_static 'js/job_result.js' %}" onerror="nb.media.handleFailure(this)"></script>
@@ -143,7 +143,9 @@
143
143
  <td>{% if value is None %}–{% else %}<code>{{ value }}</code>{% endif %}</td>
144
144
  </tr>
145
145
  {% empty %}
146
- <tr><td>—</td></tr>
146
+ <tr>
147
+ <td><span class="text-secondary">&mdash;</span></td>
148
+ </tr>
147
149
  {% endfor %}
148
150
  </table>
149
151
  </div>
@@ -2338,6 +2338,22 @@ class CustomFieldTableTest(TestCase):
2338
2338
  cf_multi_select.default = ["Foo", "Bar"]
2339
2339
  cf_multi_select.validated_save()
2340
2340
 
2341
+ # JSON custom field
2342
+ cf_json = CustomField(
2343
+ type=CustomFieldTypeChoices.TYPE_JSON,
2344
+ label="JSON Field",
2345
+ )
2346
+ cf_json.validated_save()
2347
+ cf_json.content_types.set([content_type])
2348
+
2349
+ # Markdown custom field
2350
+ cf_markdown = CustomField(
2351
+ type=CustomFieldTypeChoices.TYPE_MARKDOWN,
2352
+ label="Markdown Field",
2353
+ )
2354
+ cf_markdown.validated_save()
2355
+ cf_markdown.content_types.set([content_type])
2356
+
2341
2357
  statuses = Status.objects.get_for_model(Location)
2342
2358
 
2343
2359
  # Create a location
@@ -2346,7 +2362,7 @@ class CustomFieldTableTest(TestCase):
2346
2362
  name="Location Custom", status=statuses.first(), location_type=location_type
2347
2363
  )
2348
2364
 
2349
- # Assign custom field values for location 2
2365
+ # Assign custom field values for location
2350
2366
  self.location._custom_field_data = {
2351
2367
  cf_text.key: "bar",
2352
2368
  cf_integer.key: 456,
@@ -2355,16 +2371,39 @@ class CustomFieldTableTest(TestCase):
2355
2371
  cf_url.key: "http://example.com/2",
2356
2372
  cf_select.key: "Bar",
2357
2373
  cf_multi_select.key: ["Bar", "Baz"],
2374
+ cf_json.key: {"hello": "world"},
2375
+ cf_markdown.key: "## Heading",
2358
2376
  }
2359
2377
  self.location.validated_save()
2360
2378
 
2379
+ # Create a second location
2380
+ self.location_2 = Location.objects.create(
2381
+ name="Location Custom 2", status=statuses.first(), location_type=location_type
2382
+ )
2383
+
2384
+ # Assign custom field values for location 2
2385
+ self.location_2._custom_field_data = {
2386
+ cf_text.key: "<script></script>",
2387
+ cf_integer.key: 0,
2388
+ cf_boolean.key: False,
2389
+ cf_date.key: None,
2390
+ cf_url.key: "",
2391
+ cf_select.key: None,
2392
+ cf_multi_select.key: [],
2393
+ cf_json.key: {},
2394
+ cf_markdown.key: "",
2395
+ }
2396
+ self.location_2.validated_save()
2397
+
2398
+ self.maxDiff = None
2399
+
2361
2400
  def test_custom_field_table_render(self):
2362
- queryset = Location.objects.filter(name=self.location.name)
2401
+ queryset = Location.objects.filter(name__in=[self.location.name, self.location_2.name])
2363
2402
  location_table = LocationTable(queryset)
2364
2403
 
2365
2404
  custom_column_expected = {
2366
2405
  "text_field": "bar",
2367
- "number_field": "456",
2406
+ "number_field": 456,
2368
2407
  "boolean_field": '<span class="text-success"><i class="mdi mdi-check-bold" title="Yes"></i></span>',
2369
2408
  "date_field": "2020-01-02",
2370
2409
  "url_field": '<a href="http://example.com/2">http://example.com/2</a>',
@@ -2372,18 +2411,45 @@ class CustomFieldTableTest(TestCase):
2372
2411
  "multi_choice_field": (
2373
2412
  '<span class="badge bg-secondary">Bar</span> <span class="badge bg-secondary">Baz</span>'
2374
2413
  ),
2414
+ "json_field": '<pre><code class="language-json">{\n&quot;hello&quot;: &quot;world&quot;\n}</code></pre>',
2415
+ "markdown_field": "<h2>Heading</h2>",
2375
2416
  }
2376
2417
 
2377
2418
  bound_row = location_table.rows[0]
2378
2419
 
2379
2420
  for col_name, col_expected_value in custom_column_expected.items():
2380
- internal_col_name = "cf_" + col_name
2381
- custom_column = location_table.base_columns.get(internal_col_name)
2382
- self.assertIsNotNone(custom_column, internal_col_name)
2383
- self.assertIsInstance(custom_column, CustomFieldColumn)
2421
+ with self.subTest(col_name=col_name, col_expected_value=col_expected_value):
2422
+ internal_col_name = "cf_" + col_name
2423
+ custom_column = location_table.base_columns.get(internal_col_name)
2424
+ self.assertIsNotNone(custom_column, internal_col_name)
2425
+ self.assertIsInstance(custom_column, CustomFieldColumn)
2426
+
2427
+ rendered_value = bound_row.get_cell(internal_col_name) # pylint: disable=no-member
2428
+ self.assertHTMLEqual(str(rendered_value), str(col_expected_value))
2429
+
2430
+ custom_column_expected_2 = {
2431
+ "text_field": "<script></script>",
2432
+ "number_field": 0,
2433
+ "boolean_field": '<span class="text-danger"><i class="mdi mdi-close-thick" title="No"></i></span>',
2434
+ "date_field": '<span class="text-secondary">&mdash;</span>',
2435
+ "url_field": '<span class="text-secondary">&mdash;</span>',
2436
+ "choice_field": '<span class="text-secondary">&mdash;</span>',
2437
+ "multi_choice_field": '<span class="text-secondary">&mdash;</span>',
2438
+ "json_field": '<pre><code class="language-json">{}</code></pre>',
2439
+ "markdown_field": '<span class="text-secondary">&mdash;</span>',
2440
+ }
2441
+
2442
+ bound_row = location_table.rows[1]
2443
+
2444
+ for col_name, col_expected_value in custom_column_expected_2.items():
2445
+ with self.subTest(col_name=col_name, col_expected_value=col_expected_value):
2446
+ internal_col_name = "cf_" + col_name
2447
+ custom_column = location_table.base_columns.get(internal_col_name)
2448
+ self.assertIsNotNone(custom_column, internal_col_name)
2449
+ self.assertIsInstance(custom_column, CustomFieldColumn)
2384
2450
 
2385
- rendered_value = bound_row.get_cell(internal_col_name) # pylint: disable=no-member
2386
- self.assertEqual(rendered_value, col_expected_value)
2451
+ rendered_value = bound_row.get_cell(internal_col_name) # pylint: disable=no-member
2452
+ self.assertHTMLEqual(str(rendered_value), str(col_expected_value))
2387
2453
 
2388
2454
 
2389
2455
  class CustomFieldFilterFormTest(TestCase):