nautobot 2.4.2__py3-none-any.whl → 2.4.4__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 (267) hide show
  1. nautobot/apps/filters.py +2 -0
  2. nautobot/circuits/filters.py +1 -1
  3. nautobot/circuits/templates/circuits/inc/circuit_termination.html +1 -1
  4. nautobot/circuits/tests/integration/test_circuit.py +135 -0
  5. nautobot/circuits/tests/test_models.py +5 -3
  6. nautobot/circuits/views.py +4 -1
  7. nautobot/cloud/api/views.py +3 -3
  8. nautobot/cloud/filters.py +3 -6
  9. nautobot/cloud/tests/test_filters.py +21 -0
  10. nautobot/core/admin.py +2 -0
  11. nautobot/core/constants.py +0 -1
  12. nautobot/core/forms/__init__.py +2 -0
  13. nautobot/core/forms/forms.py +2 -1
  14. nautobot/core/forms/widgets.py +8 -0
  15. nautobot/core/jobs/__init__.py +2 -1
  16. nautobot/core/management/commands/generate_performance_test_endpoints.py +271 -0
  17. nautobot/core/models/utils.py +6 -1
  18. nautobot/core/templates/generic/object_bulk_delete.html +1 -1
  19. nautobot/core/templates/generic/object_bulk_edit.html +1 -1
  20. nautobot/core/templates/generic/object_bulk_import.html +1 -1
  21. nautobot/core/templates/generic/object_create.html +5 -0
  22. nautobot/core/templates/generic/object_delete.html +1 -1
  23. nautobot/core/templates/generic/object_detail.html +1 -1
  24. nautobot/core/templates/generic/object_edit.html +1 -1
  25. nautobot/core/templates/inc/javascript.html +3 -0
  26. nautobot/core/templates/widgets/clearable_file.html +5 -0
  27. nautobot/core/templatetags/helpers.py +3 -3
  28. nautobot/core/templatetags/ui_framework.py +20 -4
  29. nautobot/core/testing/forms.py +1 -1
  30. nautobot/core/testing/integration.py +37 -7
  31. nautobot/core/tests/test_api.py +1 -1
  32. nautobot/core/tests/test_commands.py +31 -0
  33. nautobot/core/tests/test_graphql.py +3 -3
  34. nautobot/core/tests/test_jobs.py +4 -1
  35. nautobot/core/tests/test_utils.py +17 -2
  36. nautobot/core/ui/object_detail.py +1 -1
  37. nautobot/core/utils/lookup.py +12 -1
  38. nautobot/core/views/generic.py +9 -1
  39. nautobot/core/views/mixins.py +9 -1
  40. nautobot/dcim/api/serializers.py +36 -0
  41. nautobot/dcim/api/views.py +12 -11
  42. nautobot/dcim/elevations.py +17 -4
  43. nautobot/dcim/factory.py +9 -1
  44. nautobot/dcim/filters/__init__.py +27 -1
  45. nautobot/dcim/forms.py +16 -7
  46. nautobot/dcim/models/devices.py +12 -7
  47. nautobot/dcim/signals.py +26 -0
  48. nautobot/dcim/templates/dcim/cable_trace.html +4 -4
  49. nautobot/dcim/templates/dcim/consoleport.html +14 -4
  50. nautobot/dcim/templates/dcim/consoleserverport.html +14 -4
  51. nautobot/dcim/templates/dcim/device/lldp_neighbors.html +3 -3
  52. nautobot/dcim/templates/dcim/frontport.html +7 -2
  53. nautobot/dcim/templates/dcim/interface.html +9 -4
  54. nautobot/dcim/templates/dcim/powerfeed.html +8 -3
  55. nautobot/dcim/templates/dcim/poweroutlet.html +14 -4
  56. nautobot/dcim/templates/dcim/powerport.html +14 -4
  57. nautobot/dcim/templates/dcim/rearport.html +7 -2
  58. nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +0 -62
  59. nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +6 -0
  60. nautobot/dcim/tests/integration/test_fileinputpicker.py +87 -0
  61. nautobot/dcim/tests/test_api.py +176 -0
  62. nautobot/dcim/tests/test_filters.py +56 -3
  63. nautobot/dcim/tests/test_models.py +41 -1
  64. nautobot/dcim/views.py +24 -14
  65. nautobot/extras/api/mixins.py +1 -1
  66. nautobot/extras/api/views.py +4 -4
  67. nautobot/extras/filters/__init__.py +4 -0
  68. nautobot/extras/forms/forms.py +4 -0
  69. nautobot/extras/jobs.py +8 -1
  70. nautobot/extras/models/datasources.py +7 -3
  71. nautobot/extras/plugins/__init__.py +26 -1
  72. nautobot/extras/templates/extras/inc/jobresult.html +12 -13
  73. nautobot/extras/templates/extras/job.html +1 -0
  74. nautobot/extras/templates/extras/objectchange.html +28 -12
  75. nautobot/extras/tests/test_api.py +16 -15
  76. nautobot/extras/tests/test_dynamicgroups.py +14 -0
  77. nautobot/extras/tests/test_filters.py +2 -0
  78. nautobot/extras/tests/test_plugins.py +32 -1
  79. nautobot/extras/tests/test_views.py +209 -11
  80. nautobot/extras/utils.py +30 -0
  81. nautobot/extras/views.py +32 -14
  82. nautobot/ipam/api/serializers.py +7 -8
  83. nautobot/ipam/api/views.py +5 -5
  84. nautobot/ipam/factory.py +27 -8
  85. nautobot/ipam/filters.py +67 -29
  86. nautobot/ipam/formfields.py +51 -0
  87. nautobot/ipam/forms.py +15 -7
  88. nautobot/ipam/migrations/0051_added_optional_vrf_relationship_to_vdc.py +41 -0
  89. nautobot/ipam/models.py +63 -5
  90. nautobot/ipam/tables.py +21 -7
  91. nautobot/ipam/tests/test_api.py +107 -66
  92. nautobot/ipam/tests/test_filters.py +145 -5
  93. nautobot/ipam/tests/test_views.py +15 -2
  94. nautobot/project-static/bootstrap-filestyle-1.2.3/bootstrap-filestyle.min.js +11 -0
  95. nautobot/project-static/css/base.css +11 -0
  96. nautobot/project-static/css/dark.css +2 -1
  97. nautobot/project-static/docs/apps/index.html +1 -1
  98. nautobot/project-static/docs/apps/nautobot-apps.html +1 -1
  99. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +62 -0
  100. nautobot/project-static/docs/development/apps/api/configuration-view.html +0 -3
  101. nautobot/project-static/docs/development/apps/api/models/graphql.html +9 -13
  102. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +94 -1
  103. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +2 -5
  104. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +0 -3
  105. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +0 -3
  106. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +0 -3
  107. nautobot/project-static/docs/development/apps/api/prometheus.html +0 -3
  108. nautobot/project-static/docs/development/apps/api/setup.html +1 -1
  109. nautobot/project-static/docs/development/apps/api/testing.html +0 -6
  110. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +0 -3
  111. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +0 -3
  112. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +0 -3
  113. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +0 -3
  114. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +1 -7
  115. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +0 -7
  116. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +0 -4
  117. nautobot/project-static/docs/development/apps/api/views/notes.html +0 -3
  118. nautobot/project-static/docs/development/apps/index.html +2 -35
  119. nautobot/project-static/docs/development/apps/migration/code-updates.html +7 -6
  120. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +2 -2
  121. nautobot/project-static/docs/development/apps/migration/from-v1.html +3 -3
  122. nautobot/project-static/docs/development/core/application-registry.html +0 -6
  123. nautobot/project-static/docs/development/core/best-practices.html +1 -28
  124. nautobot/project-static/docs/development/core/bootstrap-ui.html +1 -1
  125. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +65 -11
  126. nautobot/project-static/docs/development/core/getting-started.html +14 -18
  127. nautobot/project-static/docs/development/core/homepage.html +0 -3
  128. nautobot/project-static/docs/development/core/index.html +1 -1
  129. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +3 -3
  130. nautobot/project-static/docs/development/core/model-checklist.html +1 -1
  131. nautobot/project-static/docs/development/core/navigation-menu.html +1 -1
  132. nautobot/project-static/docs/development/core/release-checklist.html +1 -1
  133. nautobot/project-static/docs/development/core/settings.html +1 -1
  134. nautobot/project-static/docs/development/core/style-guide.html +4 -9
  135. nautobot/project-static/docs/development/core/templates.html +0 -3
  136. nautobot/project-static/docs/development/core/testing.html +0 -9
  137. nautobot/project-static/docs/development/jobs/index.html +11 -30
  138. nautobot/project-static/docs/development/jobs/migration/from-v1.html +3 -2
  139. nautobot/project-static/docs/index.html +3 -2
  140. nautobot/project-static/docs/objects.inv +0 -0
  141. nautobot/project-static/docs/overview/application_stack.html +2 -20
  142. nautobot/project-static/docs/release-notes/version-1.0.html +2 -2
  143. nautobot/project-static/docs/release-notes/version-1.1.html +2 -2
  144. nautobot/project-static/docs/release-notes/version-1.2.html +3 -3
  145. nautobot/project-static/docs/release-notes/version-1.3.html +1 -1
  146. nautobot/project-static/docs/release-notes/version-1.4.html +17 -17
  147. nautobot/project-static/docs/release-notes/version-1.5.html +8 -8
  148. nautobot/project-static/docs/release-notes/version-1.6.html +4 -4
  149. nautobot/project-static/docs/release-notes/version-2.0.html +10 -10
  150. nautobot/project-static/docs/release-notes/version-2.1.html +7 -7
  151. nautobot/project-static/docs/release-notes/version-2.2.html +1 -1
  152. nautobot/project-static/docs/release-notes/version-2.3.html +4 -4
  153. nautobot/project-static/docs/release-notes/version-2.4.html +379 -0
  154. nautobot/project-static/docs/requirements.txt +1 -1
  155. nautobot/project-static/docs/search/search_index.json +1 -1
  156. nautobot/project-static/docs/sitemap.xml +290 -290
  157. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  158. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +3 -3
  159. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +4 -4
  160. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +1 -1
  161. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +3 -13
  162. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +5 -5
  163. nautobot/project-static/docs/user-guide/administration/guides/docker.html +3 -18
  164. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +1 -1
  165. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +4 -4
  166. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +15 -15
  167. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +2 -2
  168. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +1 -1
  169. nautobot/project-static/docs/user-guide/administration/installation/index.html +0 -16
  170. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +1 -1
  171. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +7 -10
  172. nautobot/project-static/docs/user-guide/administration/installation/services.html +1 -12
  173. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +3 -3
  174. nautobot/project-static/docs/user-guide/administration/security/index.html +1 -1
  175. nautobot/project-static/docs/user-guide/administration/security/notices.html +1 -0
  176. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +5 -35
  177. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +1 -1
  178. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/tables/v2-code-location-changes.yaml +1 -1
  179. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +12 -9
  180. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +0 -4
  181. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +0 -3
  182. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +0 -4
  183. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +0 -4
  184. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +0 -4
  185. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +0 -4
  186. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +0 -4
  187. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +0 -4
  188. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +0 -3
  189. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +0 -4
  190. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +0 -4
  191. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +0 -4
  192. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +1 -17
  193. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +0 -3
  194. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +0 -4
  195. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +0 -4
  196. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +0 -3
  197. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +1 -7
  198. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +0 -4
  199. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +0 -4
  200. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +0 -4
  201. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +0 -4
  202. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +0 -4
  203. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +0 -4
  204. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +0 -4
  205. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +0 -6
  206. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +0 -3
  207. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +0 -4
  208. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +0 -4
  209. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +0 -8
  210. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +15 -15
  211. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +1 -1
  212. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +0 -6
  213. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +0 -3
  214. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +3 -15
  215. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1 -27
  216. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +0 -8
  217. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +3 -6
  218. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +0 -8
  219. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +0 -7
  220. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +0 -3
  221. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +0 -3
  222. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +2 -2
  223. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +6 -6
  224. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +0 -14
  225. nautobot/project-static/docs/user-guide/platform-functionality/note.html +0 -3
  226. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +1 -10
  227. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +0 -3
  228. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +0 -14
  229. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +0 -19
  230. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +3 -9
  231. nautobot/project-static/docs/user-guide/platform-functionality/status.html +0 -8
  232. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +0 -4
  233. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +1 -13
  234. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +0 -5
  235. nautobot/project-static/js/dropdown.js +28 -0
  236. nautobot/project-static/js/editor.js +292 -0
  237. nautobot/project-static/monaco-editor-0.52.2/README.md +81 -0
  238. nautobot/project-static/monaco-editor-0.52.2/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
  239. nautobot/project-static/monaco-editor-0.52.2/vs/base/worker/workerMain.js +31 -0
  240. nautobot/project-static/monaco-editor-0.52.2/vs/basic-languages/xml/xml.js +10 -0
  241. nautobot/project-static/monaco-editor-0.52.2/vs/basic-languages/yaml/yaml.js +10 -0
  242. nautobot/project-static/monaco-editor-0.52.2/vs/editor/editor.main.css +8 -0
  243. nautobot/project-static/monaco-editor-0.52.2/vs/editor/editor.main.js +798 -0
  244. nautobot/project-static/monaco-editor-0.52.2/vs/language/json/jsonMode.js +19 -0
  245. nautobot/project-static/monaco-editor-0.52.2/vs/language/json/jsonWorker.js +42 -0
  246. nautobot/project-static/monaco-editor-0.52.2/vs/loader.js +11 -0
  247. nautobot/tenancy/filters/__init__.py +3 -5
  248. nautobot/tenancy/forms.py +9 -0
  249. nautobot/tenancy/templates/tenancy/tenant_create.html +21 -0
  250. nautobot/tenancy/templates/tenancy/tenant_edit.html +2 -21
  251. nautobot/tenancy/templates/tenancy/tenantgroup.html +2 -44
  252. nautobot/tenancy/templates/tenancy/tenantgroup_retrieve.html +1 -0
  253. nautobot/tenancy/tests/test_filters.py +10 -0
  254. nautobot/tenancy/tests/test_views.py +5 -1
  255. nautobot/tenancy/urls.py +7 -79
  256. nautobot/tenancy/views.py +51 -80
  257. nautobot/virtualization/views.py +0 -1
  258. nautobot/wireless/api/serializers.py +6 -1
  259. nautobot/wireless/api/views.py +3 -3
  260. nautobot/wireless/tables.py +9 -4
  261. nautobot/wireless/tests/test_api.py +5 -9
  262. {nautobot-2.4.2.dist-info → nautobot-2.4.4.dist-info}/METADATA +9 -9
  263. {nautobot-2.4.2.dist-info → nautobot-2.4.4.dist-info}/RECORD +267 -246
  264. {nautobot-2.4.2.dist-info → nautobot-2.4.4.dist-info}/LICENSE.txt +0 -0
  265. {nautobot-2.4.2.dist-info → nautobot-2.4.4.dist-info}/NOTICE +0 -0
  266. {nautobot-2.4.2.dist-info → nautobot-2.4.4.dist-info}/WHEEL +0 -0
  267. {nautobot-2.4.2.dist-info → nautobot-2.4.4.dist-info}/entry_points.txt +0 -0
@@ -642,7 +642,7 @@ class JobViewSetBase(
642
642
  ):
643
643
  raise ValidationError(
644
644
  {
645
- "_task_queue": "_task_queue and _job_queue are both specified. Please specifiy only one or another."
645
+ "_task_queue": "_task_queue and _job_queue are both specified. Please specify only one or another."
646
646
  }
647
647
  )
648
648
 
@@ -685,7 +685,7 @@ class JobViewSetBase(
685
685
  "job_queue", None
686
686
  ):
687
687
  raise ValidationError(
688
- {"task_queue": "task_queue and job_queue are both specified. Please specifiy only one or another."}
688
+ {"task_queue": "task_queue and job_queue are both specified. Please specify only one or another."}
689
689
  )
690
690
  schedule_data = input_serializer.validated_data.get("schedule", None)
691
691
 
@@ -836,7 +836,7 @@ class JobQueueViewSet(NautobotModelViewSet):
836
836
  filterset_class = filters.JobQueueFilterSet
837
837
 
838
838
 
839
- class JobQueueAssignmentViewSet(NautobotModelViewSet):
839
+ class JobQueueAssignmentViewSet(ModelViewSet):
840
840
  """
841
841
  Manage job queue assignments through DELETE, GET, POST, PUT, and PATCH requests.
842
842
  """
@@ -1069,7 +1069,7 @@ class MetadataChoiceViewSet(ModelViewSet):
1069
1069
  filterset_class = filters.MetadataChoiceFilterSet
1070
1070
 
1071
1071
 
1072
- class ObjectMetadataViewSet(NautobotModelViewSet):
1072
+ class ObjectMetadataViewSet(ModelViewSet):
1073
1073
  queryset = ObjectMetadata.objects.all()
1074
1074
  serializer_class = serializers.ObjectMetadataSerializer
1075
1075
  filterset_class = filters.ObjectMetadataFilterSet
@@ -854,6 +854,10 @@ class JobFilterSet(BaseFilterSet, CustomFieldModelFilterSetMixin):
854
854
  "description": "icontains",
855
855
  },
856
856
  )
857
+ job_queues = NaturalKeyOrPKMultipleChoiceFilter(
858
+ queryset=JobQueue.objects.all(),
859
+ label="Job Queue (name or ID)",
860
+ )
857
861
 
858
862
  class Meta:
859
863
  model = Job
@@ -38,6 +38,7 @@ from nautobot.core.forms import (
38
38
  )
39
39
  from nautobot.core.forms.constants import BOOLEAN_WITH_BLANK_CHOICES
40
40
  from nautobot.core.forms.forms import ConfirmationForm
41
+ from nautobot.core.forms.widgets import ClearableFileInput
41
42
  from nautobot.core.utils.deprecation import class_deprecated_in_favor_of
42
43
  from nautobot.dcim.models import Device, DeviceRedundancyGroup, DeviceType, Location, Platform
43
44
  from nautobot.extras.choices import (
@@ -985,6 +986,9 @@ class ImageAttachmentForm(BootstrapMixin, forms.ModelForm):
985
986
  "name",
986
987
  "image",
987
988
  ]
989
+ widgets = {
990
+ "image": ClearableFileInput,
991
+ }
988
992
 
989
993
 
990
994
  #
nautobot/extras/jobs.py CHANGED
@@ -37,6 +37,7 @@ from nautobot.core.forms import (
37
37
  DynamicModelMultipleChoiceField,
38
38
  JSONField,
39
39
  )
40
+ from nautobot.core.forms.widgets import ClearableFileInput
40
41
  from nautobot.core.utils.config import get_settings_or_config
41
42
  from nautobot.core.utils.logging import sanitize
42
43
  from nautobot.core.utils.lookup import get_model_from_name
@@ -1040,12 +1041,18 @@ class DatabaseFileField(forms.FileField):
1040
1041
  widget = DBClearableFileInput
1041
1042
 
1042
1043
 
1044
+ class BootstrapStyleFileField(forms.FileField):
1045
+ """File picker with UX bootstrap style and clearable checkbox."""
1046
+
1047
+ widget = ClearableFileInput
1048
+
1049
+
1043
1050
  class FileVar(ScriptVariable):
1044
1051
  """
1045
1052
  An uploaded file.
1046
1053
  """
1047
1054
 
1048
- form_field = DatabaseFileField
1055
+ form_field = BootstrapStyleFileField
1049
1056
 
1050
1057
 
1051
1058
  class IPAddressVar(ScriptVariable):
@@ -131,16 +131,20 @@ class GitRepository(PrimaryModel):
131
131
 
132
132
  def get_latest_sync(self):
133
133
  """
134
- Return a `JobResult` for the latest sync operation.
134
+ Return a `JobResult` for the latest sync operation if one has occurred.
135
135
 
136
136
  Returns:
137
- JobResult
137
+ Returns a `JobResult` if the repo has been synced before, otherwise returns None.
138
138
  """
139
139
  from nautobot.extras.models import JobResult
140
140
 
141
141
  # This will match all "GitRepository" jobs (pull/refresh, dry-run, etc.)
142
142
  prefix = "nautobot.core.jobs.GitRepository"
143
- return JobResult.objects.filter(task_name__startswith=prefix, task_kwargs__repository=self.pk).latest()
143
+
144
+ if JobResult.objects.filter(task_name__startswith=prefix, task_kwargs__repository=self.pk).exists():
145
+ return JobResult.objects.filter(task_name__startswith=prefix, task_kwargs__repository=self.pk).latest()
146
+ else:
147
+ return None
144
148
 
145
149
  def to_csv(self):
146
150
  return (
@@ -728,6 +728,31 @@ def register_plugin_menu_items(section_name, menu_items):
728
728
  #
729
729
 
730
730
 
731
+ class CustomValidatorContext(dict):
732
+ def __init__(self, obj):
733
+ """
734
+ If there is an active change context, meaning we are in a web request context,
735
+ we have access to the current user object. Otherwise, we are likely running inside
736
+ a management command or other non-web or non-Job context, and we should use an AnonymousUser.
737
+ This ensures people's custom validators don't outright break when running in non-web
738
+ contexts, and should generally provide a sane default, given validation based on the
739
+ user is commonly going to be least-privelege based, and thus the AnonymousUser will
740
+ cause such validation logic to fail closed.
741
+ """
742
+ from django.contrib.auth.models import AnonymousUser
743
+
744
+ from nautobot.extras.signals import change_context_state
745
+
746
+ change_context = change_context_state.get()
747
+ user = None
748
+ if change_context:
749
+ user = change_context.get_user()
750
+ if user is None:
751
+ user = AnonymousUser()
752
+
753
+ super().__init__(object=obj, user=user)
754
+
755
+
731
756
  class CustomValidator:
732
757
  """
733
758
  This class is used to register plugin custom model validators which act on specified models. It contains the clean
@@ -742,7 +767,7 @@ class CustomValidator:
742
767
  model = None
743
768
 
744
769
  def __init__(self, obj):
745
- self.context = {"object": obj}
770
+ self.context = CustomValidatorContext(obj)
746
771
 
747
772
  def validation_error(self, message):
748
773
  """
@@ -12,45 +12,43 @@
12
12
  <strong>Summary of Results</strong>
13
13
  </div>
14
14
  <table class="table table-hover panel-body">
15
- {% if result.job_model is not None %}
16
15
  <tr>
17
16
  <td>Job Description</td>
18
- <td>{{ result.job_model.description | render_markdown }}</td>
17
+ <td>{{ result.job_model.description | render_markdown | placeholder }}</td>
19
18
  </tr>
20
- {% endif %}
21
19
  <tr>
22
20
  <td>Status</td>
23
21
  <td><span id="pending-result-label">{% include 'extras/inc/job_label.html' with result=result %}</span></td>
24
22
  </tr>
25
23
  <tr>
26
24
  <td>Started at</td>
27
- <td>{{ result.date_created }}</td>
25
+ <td>{{ result.date_created | placeholder }}</td>
28
26
  </tr>
29
27
  <tr>
30
28
  <td>User</td>
31
- <td>{{ result.user }}</td>
29
+ <td>{{ result.user | placeholder }}</td>
32
30
  </tr>
33
31
  <tr>
34
32
  <td>Duration</td>
35
33
  <td>
36
- {% if result.date_done %}
37
- {{ result.duration }}
38
- {% else %}
34
+ {% if result.date_created and not result.date_done %}
39
35
  <img src="{% static 'img/ajax-loader.gif' %}">
36
+ {% else %}
37
+ {{ result.duration | placeholder}}
40
38
  {% endif %}
41
39
  </td>
42
40
  </tr>
43
41
  <tr>
44
42
  <td>Return Value</td>
45
43
  <td>
46
- {% if result.date_done %}
44
+ {% if result.date_created and not result.date_done %}
45
+ <img src="{% static 'img/ajax-loader.gif' %}">
46
+ {% else %}
47
47
  {% if result.result %}
48
48
  <pre>{{ result.result | render_json }}</pre>
49
49
  {% else %}
50
50
  {{ result.result | placeholder }}
51
51
  {% endif %}
52
- {% else %}
53
- <img src="{% static 'img/ajax-loader.gif' %}">
54
52
  {% endif %}
55
53
  </td>
56
54
  </tr>
@@ -89,6 +87,7 @@
89
87
  <input class="form-control" id="log-filter" type="text" placeholder="Filter log level or message" title="Filter log level or message" style="height: 23px" />
90
88
  </div>
91
89
  </div>
92
- {% ajax_table "log_table" "extras:jobresult_log-table" pk=result.pk %}
90
+ {% if result and result.pk %}
91
+ {% ajax_table "log_table" "extras:jobresult_log-table" pk=result.pk %}
92
+ {% endif %}
93
93
  </div>
94
-
@@ -192,4 +192,5 @@
192
192
  toggleExecutionType();
193
193
  });
194
194
  </script>
195
+ {{ job_form.media }}
195
196
  {% endblock %}
@@ -1,5 +1,6 @@
1
1
  {% extends 'generic/object_retrieve.html' %}
2
2
  {% load helpers %}
3
+ {% load static %}
3
4
 
4
5
  {% block title %}{{ object }}{% endblock %}
5
6
 
@@ -95,6 +96,20 @@
95
96
  </tr>
96
97
  </table>
97
98
  </div>
99
+ <div class="panel panel-default">
100
+ <div class="panel-heading">
101
+ <strong>Object Data</strong>
102
+ </div>
103
+ <div class="panel-body">
104
+ <div class="editor-container"
105
+ data-lang="json"
106
+ data-value="{{ object.object_data|render_json:False }}"
107
+ style="max-height: 300px">
108
+ </div>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ <div class="col-md-7">
98
113
  <div class="panel panel-default">
99
114
  <div class="panel-heading">
100
115
  <strong>Difference</strong>
@@ -119,22 +134,17 @@
119
134
  {% endif %}
120
135
  </span>
121
136
  {% else %}
122
- <pre class="diff-removed">{{ diff_removed|render_json }}</pre>
123
- <pre class="diff-added">{{ diff_added|render_json }}</pre>
137
+ <div class="editor-container"
138
+ data-mode="diff"
139
+ data-original="{{ diff_removed | render_json:False }}"
140
+ data-modified="{{ diff_added | render_json:False }}"
141
+ data-lang="json"
142
+ style="max-height: 730px">
143
+ </div>
124
144
  {% endif %}
125
145
  </div>
126
146
  </div>
127
147
  </div>
128
- <div class="col-md-7">
129
- <div class="panel panel-default">
130
- <div class="panel-heading">
131
- <strong>Object Data</strong>
132
- </div>
133
- <div class="panel-body">
134
- <pre>{{ object.object_data|render_json }}</pre>
135
- </div>
136
- </div>
137
- </div>
138
148
  </div>
139
149
  <div class="row">
140
150
  <div class="col-md-12">
@@ -147,3 +157,9 @@
147
157
  </div>
148
158
  </div>
149
159
  {% endblock %}
160
+
161
+ {% block javascript %}
162
+ {{ block.super }}
163
+ <script src="{% static 'js/editor.js' %}"></script>
164
+ {% endblock %}
165
+
@@ -196,20 +196,6 @@ class ComputedFieldTest(APIViewTestCases.APIViewTestCase):
196
196
 
197
197
  class ConfigContextTest(APIViewTestCases.APIViewTestCase):
198
198
  model = ConfigContext
199
- create_data = [
200
- {
201
- "name": "Config Context 4",
202
- "data": {"more_foo": True},
203
- },
204
- {
205
- "name": "Config Context 5",
206
- "data": {"more_bar": False},
207
- },
208
- {
209
- "name": "Config Context 6",
210
- "data": {"more_baz": None},
211
- },
212
- ]
213
199
  bulk_update_data = {
214
200
  "description": "New description",
215
201
  }
@@ -220,6 +206,21 @@ class ConfigContextTest(APIViewTestCases.APIViewTestCase):
220
206
  ConfigContext.objects.create(name="Config Context 1", weight=100, data={"foo": 123})
221
207
  ConfigContext.objects.create(name="Config Context 2", weight=200, data={"bar": 456})
222
208
  ConfigContext.objects.create(name="Config Context 3", weight=300, data={"baz": 789})
209
+ cls.create_data = [
210
+ {
211
+ "name": "Config Context 4",
212
+ "data": {"more_foo": True},
213
+ "tags": [tag.pk for tag in Tag.objects.get_for_model(Device)],
214
+ },
215
+ {
216
+ "name": "Config Context 5",
217
+ "data": {"more_bar": False},
218
+ },
219
+ {
220
+ "name": "Config Context 6",
221
+ "data": {"more_baz": None},
222
+ },
223
+ ]
223
224
 
224
225
  def test_render_configcontext_for_object(self):
225
226
  """
@@ -1797,7 +1798,7 @@ class JobTest(
1797
1798
  )
1798
1799
  self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
1799
1800
  self.assertIn(
1800
- "task_queue and job_queue are both specified. Please specifiy only one or another.", str(response.content)
1801
+ "task_queue and job_queue are both specified. Please specify only one or another.", str(response.content)
1801
1802
  )
1802
1803
 
1803
1804
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
@@ -667,6 +667,20 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
667
667
  # Cleanup because we're using class-based fixtures in `setUpTestData()`
668
668
  group.refresh_from_db()
669
669
 
670
+ def test_set_filter_on_ipaddress_dynamic_group(self):
671
+ """
672
+ Test `DynamicGroup.set_filter()` for an IPAddress Dynamic Group.
673
+ https://github.com/nautobot/nautobot/issues/6805
674
+ """
675
+ ipaddress_dg = DynamicGroup.objects.create(
676
+ name="IP Address Dynamic Group",
677
+ content_type=ContentType.objects.get_for_model(IPAddress),
678
+ description="IP Address Dynamic Group",
679
+ )
680
+ # Test the fact that set_filter correctly discard an empty PrefixQuerySet
681
+ ipaddress_dg.set_filter({"parent": Prefix.objects.none()})
682
+ self.assertEqual(ipaddress_dg.filter, {})
683
+
670
684
  def test_add_child(self):
671
685
  """Test `DynamicGroup.add_child()`."""
672
686
  self.parent.add_child(
@@ -880,6 +880,8 @@ class JobFilterSetTestCase(FilterTestCases.FilterTestCase):
880
880
  generic_filter_tests = (
881
881
  ("grouping",),
882
882
  ("job_class_name",),
883
+ ("job_queues", "job_queues__id"),
884
+ ("job_queues", "job_queues__name"),
883
885
  ("module_name",),
884
886
  ("name",),
885
887
  )
@@ -17,11 +17,12 @@ from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Man
17
17
  from nautobot.dcim.tests.test_views import create_test_device
18
18
  from nautobot.extras import plugins
19
19
  from nautobot.extras.choices import CustomFieldTypeChoices, RelationshipTypeChoices
20
+ from nautobot.extras.context_managers import web_request_context
20
21
  from nautobot.extras.jobs import get_job
21
22
  from nautobot.extras.models import CustomField, Relationship, RelationshipAssociation, Role, Secret, Status
22
23
  from nautobot.extras.plugins.exceptions import PluginImproperlyConfigured
23
24
  from nautobot.extras.plugins.utils import load_plugin
24
- from nautobot.extras.plugins.validators import wrap_model_clean_methods
25
+ from nautobot.extras.plugins.validators import CustomValidator, wrap_model_clean_methods
25
26
  from nautobot.extras.plugins.views import extract_app_data
26
27
  from nautobot.extras.registry import DatasourceContent, registry
27
28
  from nautobot.ipam.models import IPAddress, Namespace, Prefix
@@ -478,6 +479,16 @@ class AppAPITest(APIViewTestCases.APIViewTestCase):
478
479
  pass
479
480
 
480
481
 
482
+ class TestUserContextCustomValidator(CustomValidator):
483
+ model = "dcim.locationtype"
484
+
485
+ def clean(self):
486
+ """
487
+ Used to validate that the correct user context is available in the custom validator.
488
+ """
489
+ self.validation_error(self.context["user"])
490
+
491
+
481
492
  class AppCustomValidationTest(TestCase):
482
493
  def setUp(self):
483
494
  # When creating a fresh test DB, wrapping model clean methods fails, which is normal.
@@ -485,6 +496,7 @@ class AppCustomValidationTest(TestCase):
485
496
  # must manually call the method again to actually perform the action, now that the
486
497
  # ContentType table has been created.
487
498
  wrap_model_clean_methods()
499
+ super().setUp()
488
500
 
489
501
  def test_custom_validator_raises_exception(self):
490
502
  location_type = LocationType.objects.get(name="Campus")
@@ -513,6 +525,25 @@ class AppCustomValidationTest(TestCase):
513
525
  with self.assertRaises(ValidationError):
514
526
  relationship_assoc.clean()
515
527
 
528
+ def test_custom_validator_non_web_request_uses_anonymous_user(self):
529
+ location_type = LocationType.objects.get(name="Campus")
530
+ registry["plugin_custom_validators"]["dcim.locationtype"] = [TestUserContextCustomValidator]
531
+
532
+ from django.contrib.auth.models import AnonymousUser
533
+
534
+ with self.assertRaises(ValidationError) as context:
535
+ location_type.clean()
536
+ self.assertEqual(context.exception.message, AnonymousUser())
537
+
538
+ def test_custom_validator_web_request_uses_real_user(self):
539
+ location_type = LocationType.objects.get(name="Campus")
540
+ registry["plugin_custom_validators"]["dcim.locationtype"] = [TestUserContextCustomValidator]
541
+
542
+ with self.assertRaises(ValidationError) as context:
543
+ with web_request_context(user=self.user):
544
+ location_type.clean()
545
+ self.assertEqual(context.exception.message, self.user)
546
+
516
547
 
517
548
  class ExampleModelCustomActionViewTest(TestCase):
518
549
  """Test for custom action view `all_names` added to Example App"""