nautobot 3.0.0a2__py3-none-any.whl → 3.0.0a3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of nautobot might be problematic. Click here for more details.

Files changed (420) hide show
  1. nautobot/apps/choices.py +0 -2
  2. nautobot/apps/filters.py +7 -9
  3. nautobot/apps/models.py +2 -2
  4. nautobot/apps/ui.py +9 -1
  5. nautobot/circuits/filters.py +3 -2
  6. nautobot/circuits/navigation.py +3 -2
  7. nautobot/circuits/templates/circuits/circuit.html +1 -1
  8. nautobot/circuits/templates/circuits/circuit_create.html +3 -3
  9. nautobot/circuits/templates/circuits/circuittermination.html +1 -1
  10. nautobot/circuits/templates/circuits/circuittermination_create.html +9 -24
  11. nautobot/circuits/templates/circuits/circuittype.html +1 -1
  12. nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +6 -6
  13. nautobot/circuits/templates/circuits/inc/speed_widget.html +12 -12
  14. nautobot/circuits/templates/circuits/providernetwork.html +1 -1
  15. nautobot/circuits/tests/integration/test_circuit.py +10 -13
  16. nautobot/cloud/filters.py +1 -1
  17. nautobot/cloud/navigation.py +3 -2
  18. nautobot/core/api/schema.py +1 -1
  19. nautobot/core/api/serializers.py +6 -1
  20. nautobot/core/api/urls.py +1 -0
  21. nautobot/core/api/views.py +8 -0
  22. nautobot/core/apps/__init__.py +11 -10
  23. nautobot/core/celery/__init__.py +3 -5
  24. nautobot/core/checks.py +46 -0
  25. nautobot/core/cli/bootstrap_v3_to_v5.py +70 -1
  26. nautobot/core/cli/migrate_deprecated_templates.py +200 -0
  27. nautobot/core/constants.py +3 -0
  28. nautobot/core/context_processors.py +9 -1
  29. nautobot/core/forms/forms.py +1 -1
  30. nautobot/core/jobs/__init__.py +6 -3
  31. nautobot/core/jobs/groups.py +31 -1
  32. nautobot/core/management/commands/generate_test_data.py +28 -9
  33. nautobot/core/models/generics.py +9 -1
  34. nautobot/core/models/tree_queries.py +10 -5
  35. nautobot/core/settings.py +18 -12
  36. nautobot/core/settings.yaml +13 -7
  37. nautobot/core/signals.py +12 -1
  38. nautobot/core/tables.py +13 -6
  39. nautobot/core/templates/40x.html +1 -1
  40. nautobot/core/templates/500.html +2 -2
  41. nautobot/core/templates/admin/config/config.html +12 -12
  42. nautobot/core/templates/admin/index.html +3 -3
  43. nautobot/core/templates/buttons/export.html +1 -1
  44. nautobot/core/templates/components/button/dropdown.html +5 -3
  45. nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
  46. nautobot/core/templates/components/panel/panel.html +3 -3
  47. nautobot/core/templates/components/tab/content_wrapper.html +2 -3
  48. nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +1 -1
  49. nautobot/core/templates/echarts/echarts.html +1 -1
  50. nautobot/core/templates/generic/object_bulk_add_component.html +2 -1
  51. nautobot/core/templates/generic/object_bulk_create.html +4 -3
  52. nautobot/core/templates/generic/object_bulk_destroy.html +3 -3
  53. nautobot/core/templates/generic/object_bulk_remove.html +2 -2
  54. nautobot/core/templates/generic/object_bulk_update.html +5 -4
  55. nautobot/core/templates/generic/object_create.html +5 -4
  56. nautobot/core/templates/generic/object_import.html +2 -1
  57. nautobot/core/templates/generic/object_list.html +12 -4
  58. nautobot/core/templates/generic/object_notes.html +5 -3
  59. nautobot/core/templates/generic/object_retrieve.html +2 -3
  60. nautobot/core/templates/graphene/graphiql.html +7 -7
  61. nautobot/core/templates/home.html +1 -1
  62. nautobot/core/templates/import_success.html +2 -1
  63. nautobot/core/templates/inc/computed_fields/panel_data.html +1 -1
  64. nautobot/core/templates/inc/created_updated.html +7 -3
  65. nautobot/core/templates/inc/custom_fields/panel_data.html +1 -1
  66. nautobot/core/templates/inc/form_static_field.html +6 -0
  67. nautobot/core/templates/inc/header.html +1 -1
  68. nautobot/core/templates/inc/image_attachments.html +2 -1
  69. nautobot/core/templates/inc/nav_menu.html +2 -1
  70. nautobot/core/templates/inc/search_panel.html +4 -4
  71. nautobot/core/templates/login.html +4 -2
  72. nautobot/core/templates/nautobot_config.py.j2 +6 -5
  73. nautobot/core/templates/redoc_ui.html +7 -0
  74. nautobot/core/templates/search.html +1 -1
  75. nautobot/core/templates/swagger_ui.html +17 -3
  76. nautobot/core/templates/system_jobs/import_objects.html +1 -2
  77. nautobot/core/templates/utilities/confirmation_form.html +2 -2
  78. nautobot/core/templates/utilities/obj_table.html +10 -2
  79. nautobot/core/templates/utilities/render_field.html +7 -7
  80. nautobot/core/templates/utilities/render_jinja2.html +2 -2
  81. nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +4 -4
  82. nautobot/core/templates/utilities/theme_preview.html +16 -3
  83. nautobot/core/templates/widgets/selectwithdisabled_option.html +3 -1
  84. nautobot/core/templatetags/helpers.py +52 -6
  85. nautobot/core/testing/api.py +68 -9
  86. nautobot/core/testing/filters.py +0 -23
  87. nautobot/core/testing/integration.py +23 -10
  88. nautobot/core/testing/mixins.py +2 -0
  89. nautobot/core/testing/views.py +4 -0
  90. nautobot/core/tests/integration/test_app_home.py +34 -30
  91. nautobot/core/tests/integration/test_app_navbar.py +3 -0
  92. nautobot/core/tests/nautobot_config_without_example_apps.py +4 -0
  93. nautobot/core/tests/runner.py +9 -1
  94. nautobot/core/tests/test_api.py +5 -3
  95. nautobot/core/tests/test_breadcrumbs.py +6 -7
  96. nautobot/core/tests/test_checks.py +28 -0
  97. nautobot/core/tests/test_cli.py +40 -0
  98. nautobot/core/tests/test_config.py +2 -1
  99. nautobot/core/tests/test_forms.py +55 -13
  100. nautobot/core/tests/test_jobs.py +75 -1
  101. nautobot/core/tests/test_nautobot_server.py +2 -0
  102. nautobot/core/tests/test_navigations.py +76 -1
  103. nautobot/core/tests/test_patch_social_django.py +42 -0
  104. nautobot/core/tests/test_tables.py +3 -1
  105. nautobot/core/tests/test_templatetags_helpers.py +53 -13
  106. nautobot/core/tests/test_templatetags_ui_framework.py +4 -4
  107. nautobot/core/tests/test_tree_queries.py +14 -1
  108. nautobot/core/tests/test_ui.py +1 -1
  109. nautobot/core/tests/test_utils.py +31 -4
  110. nautobot/core/tests/test_views.py +159 -31
  111. nautobot/core/ui/breadcrumbs.py +2 -12
  112. nautobot/core/ui/choices.py +142 -10
  113. nautobot/core/ui/constants.py +76 -12
  114. nautobot/core/ui/object_detail.py +92 -12
  115. nautobot/core/urls.py +12 -1
  116. nautobot/core/utils/cache.py +2 -1
  117. nautobot/core/utils/filtering.py +17 -17
  118. nautobot/core/utils/lookup.py +3 -8
  119. nautobot/core/utils/module_loading.py +21 -0
  120. nautobot/core/utils/patch_social_django.py +128 -0
  121. nautobot/core/views/__init__.py +38 -1
  122. nautobot/core/views/generic.py +3 -3
  123. nautobot/core/views/mixins.py +15 -3
  124. nautobot/core/views/renderers.py +2 -0
  125. nautobot/core/views/viewsets.py +2 -1
  126. nautobot/data_validation/apps.py +1 -5
  127. nautobot/data_validation/custom_validators.py +4 -4
  128. nautobot/data_validation/filters.py +1 -1
  129. nautobot/data_validation/forms.py +40 -0
  130. nautobot/data_validation/migrations/0001_initial.py +0 -7
  131. nautobot/data_validation/migrations/0002_data_migration_from_app.py +0 -12
  132. nautobot/data_validation/models.py +16 -7
  133. nautobot/data_validation/navigation.py +8 -1
  134. nautobot/data_validation/tables.py +12 -5
  135. nautobot/data_validation/templates/data_validation/datacompliance_tab.html +1 -0
  136. nautobot/data_validation/templates/data_validation/device_constraints.html +61 -0
  137. nautobot/data_validation/tests/__init__.py +2 -2
  138. nautobot/data_validation/tests/migrations/test_migrations.py +83 -3
  139. nautobot/data_validation/tests/test_data_compliance_rules.py +12 -7
  140. nautobot/data_validation/tests/test_filters.py +8 -6
  141. nautobot/data_validation/tests/test_models.py +15 -0
  142. nautobot/data_validation/tests/test_views.py +190 -32
  143. nautobot/data_validation/urls.py +2 -5
  144. nautobot/data_validation/views.py +73 -40
  145. nautobot/dcim/api/serializers.py +0 -13
  146. nautobot/dcim/apps.py +4 -0
  147. nautobot/dcim/choices.py +16 -0
  148. nautobot/dcim/custom_validators.py +84 -0
  149. nautobot/dcim/filter_mixins.py +353 -4
  150. nautobot/dcim/{filters/__init__.py → filters.py} +2 -35
  151. nautobot/dcim/forms.py +1 -1
  152. nautobot/dcim/migrations/0078_remove_device_location_tenant_name_uniqueness.py +16 -0
  153. nautobot/dcim/migrations/0079_device_name_data_migration.py +59 -0
  154. nautobot/dcim/models/device_components.py +81 -68
  155. nautobot/dcim/models/devices.py +13 -16
  156. nautobot/dcim/navigation.py +7 -6
  157. nautobot/dcim/tables/devices.py +3 -0
  158. nautobot/dcim/tables/template_code.py +14 -14
  159. nautobot/dcim/templates/dcim/cable.html +2 -61
  160. nautobot/dcim/templates/dcim/cable_connect.html +28 -112
  161. nautobot/dcim/templates/dcim/cable_edit.html +2 -5
  162. nautobot/dcim/templates/dcim/cable_retrieve.html +61 -0
  163. nautobot/dcim/templates/dcim/cable_trace.html +1 -3
  164. nautobot/dcim/templates/dcim/cable_update.html +5 -0
  165. nautobot/dcim/templates/dcim/consoleport.html +6 -5
  166. nautobot/dcim/templates/dcim/consoleserverport.html +6 -5
  167. nautobot/dcim/templates/dcim/device/config.html +2 -2
  168. nautobot/dcim/templates/dcim/device/consoleports.html +1 -1
  169. nautobot/dcim/templates/dcim/device/consoleserverports.html +1 -1
  170. nautobot/dcim/templates/dcim/device/devicebays.html +1 -1
  171. nautobot/dcim/templates/dcim/device/frontports.html +1 -1
  172. nautobot/dcim/templates/dcim/device/interfaces.html +1 -1
  173. nautobot/dcim/templates/dcim/device/inventory.html +1 -1
  174. nautobot/dcim/templates/dcim/device/lldp_neighbors.html +1 -1
  175. nautobot/dcim/templates/dcim/device/modulebays.html +1 -1
  176. nautobot/dcim/templates/dcim/device/poweroutlets.html +1 -1
  177. nautobot/dcim/templates/dcim/device/powerports.html +1 -1
  178. nautobot/dcim/templates/dcim/device/rearports.html +1 -1
  179. nautobot/dcim/templates/dcim/device/status.html +8 -8
  180. nautobot/dcim/templates/dcim/device/wireless.html +1 -1
  181. nautobot/dcim/templates/dcim/device.html +1 -1
  182. nautobot/dcim/templates/dcim/device_component_add.html +2 -2
  183. nautobot/dcim/templates/dcim/device_create.html +5 -3
  184. nautobot/dcim/templates/dcim/device_interface_delete.html +1 -1
  185. nautobot/dcim/templates/dcim/device_list.html +73 -10
  186. nautobot/dcim/templates/dcim/devicebay_populate.html +2 -2
  187. nautobot/dcim/templates/dcim/devicetype.html +1 -1
  188. nautobot/dcim/templates/dcim/devicetype_component_add.html +2 -2
  189. nautobot/dcim/templates/dcim/footer_convert_to_contact_or_team_record.html +14 -0
  190. nautobot/dcim/templates/dcim/frontport.html +9 -8
  191. nautobot/dcim/templates/dcim/inc/edit_form_softwareversion_js.html +2 -2
  192. nautobot/dcim/templates/dcim/interface.html +26 -6
  193. nautobot/dcim/templates/dcim/interface_bulk_delete.html +1 -1
  194. nautobot/dcim/templates/dcim/inventoryitem_add.html +3 -1
  195. nautobot/dcim/templates/dcim/inventoryitem_bulk_delete.html +1 -1
  196. nautobot/dcim/templates/dcim/inventoryitem_edit.html +3 -1
  197. nautobot/dcim/templates/dcim/location_retrieve.html +1 -242
  198. nautobot/dcim/templates/dcim/module/base.html +49 -9
  199. nautobot/dcim/templates/dcim/module_list.html +57 -8
  200. nautobot/dcim/templates/dcim/modulefamily_retrieve.html +1 -1
  201. nautobot/dcim/templates/dcim/moduletype_retrieve.html +49 -9
  202. nautobot/dcim/templates/dcim/platform_create.html +1 -1
  203. nautobot/dcim/templates/dcim/powerfeed.html +1 -1
  204. nautobot/dcim/templates/dcim/powerpanel.html +1 -1
  205. nautobot/dcim/templates/dcim/powerport.html +5 -4
  206. nautobot/dcim/templates/dcim/rack_elevation_list.html +16 -4
  207. nautobot/dcim/templates/dcim/rack_retrieve.html +33 -15
  208. nautobot/dcim/templates/dcim/rearport.html +7 -6
  209. nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
  210. nautobot/dcim/templates/dcim/virtualchassis_add_member.html +16 -14
  211. nautobot/dcim/templates/dcim/virtualchassis_update.html +14 -6
  212. nautobot/dcim/tests/integration/test_controller.py +1 -0
  213. nautobot/dcim/tests/test_api.py +8 -0
  214. nautobot/dcim/tests/test_custom_validators.py +229 -0
  215. nautobot/dcim/tests/test_filters.py +12 -6
  216. nautobot/dcim/tests/test_models.py +63 -4
  217. nautobot/dcim/tests/test_views.py +63 -22
  218. nautobot/dcim/urls.py +64 -21
  219. nautobot/dcim/utils.py +3 -3
  220. nautobot/dcim/views.py +547 -273
  221. nautobot/extras/api/views.py +9 -1
  222. nautobot/extras/choices.py +2 -13
  223. nautobot/extras/{filters/mixins.py → filter_mixins.py} +1 -1
  224. nautobot/extras/{filters/customfields.py → filter_mixins_customfields.py} +42 -6
  225. nautobot/extras/{filters/__init__.py → filters.py} +14 -46
  226. nautobot/extras/forms/forms.py +5 -13
  227. nautobot/extras/forms/mixins.py +0 -41
  228. nautobot/extras/management/__init__.py +9 -0
  229. nautobot/extras/migrations/0127_approval_workflow_models.py +6 -6
  230. nautobot/extras/migrations/0129_jobresult_debug_log_count_jobresult_error_log_count_and_more.py +37 -0
  231. nautobot/extras/migrations/0130_jobresult_generate_log_entry_counts.py +42 -0
  232. nautobot/extras/models/__init__.py +1 -2
  233. nautobot/extras/models/approvals.py +22 -13
  234. nautobot/extras/models/contacts.py +2 -0
  235. nautobot/extras/models/groups.py +44 -5
  236. nautobot/extras/models/jobs.py +59 -1
  237. nautobot/extras/models/mixins.py +28 -0
  238. nautobot/extras/models/models.py +13 -0
  239. nautobot/extras/models/secrets.py +1 -0
  240. nautobot/extras/models/statuses.py +0 -15
  241. nautobot/extras/navigation.py +13 -9
  242. nautobot/extras/plugins/__init__.py +33 -55
  243. nautobot/extras/plugins/tables.py +3 -3
  244. nautobot/extras/plugins/urls.py +2 -21
  245. nautobot/extras/plugins/utils.py +1 -33
  246. nautobot/extras/plugins/views.py +0 -4
  247. nautobot/extras/signals.py +20 -19
  248. nautobot/extras/tables.py +52 -68
  249. nautobot/extras/templates/extras/approval_dashboard.html +7 -5
  250. nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +4 -2
  251. nautobot/extras/templates/extras/approvalworkflowstage_retrieve.html +20 -12
  252. nautobot/extras/templates/extras/computedfield.html +1 -1
  253. nautobot/extras/templates/extras/configcontext.html +1 -1
  254. nautobot/extras/templates/extras/configcontextschema_validation.html +2 -2
  255. nautobot/extras/templates/extras/customfield.html +1 -1
  256. nautobot/extras/templates/extras/dynamicgroup_retrieve.html +11 -5
  257. nautobot/extras/templates/extras/dynamicgroup_update.html +1 -1
  258. nautobot/extras/templates/extras/gitrepository_result.html +0 -2
  259. nautobot/extras/templates/extras/graphqlquery_retrieve.html +1 -96
  260. nautobot/extras/templates/extras/inc/approval_buttons_column.html +20 -6
  261. nautobot/extras/templates/extras/inc/bulk_edit_overridable_field.html +8 -7
  262. nautobot/extras/templates/extras/inc/configcontext_format.html +10 -3
  263. nautobot/extras/templates/extras/inc/graphqlquery_execute.html +71 -0
  264. nautobot/extras/templates/extras/inc/job_tiles.html +15 -3
  265. nautobot/extras/templates/extras/inc/json_format.html +10 -3
  266. nautobot/extras/templates/extras/inc/overridable_field.html +13 -12
  267. nautobot/extras/templates/extras/job.html +29 -12
  268. nautobot/extras/templates/extras/job_bulk_edit.html +18 -0
  269. nautobot/extras/templates/extras/job_edit.html +52 -46
  270. nautobot/extras/templates/extras/job_list.html +29 -25
  271. nautobot/extras/templates/extras/marketplace.html +5 -9
  272. nautobot/extras/templates/extras/object_configcontext.html +1 -1
  273. nautobot/extras/templates/extras/object_dynamicgroups.html +2 -2
  274. nautobot/extras/templates/extras/objectchange_retrieve.html +19 -37
  275. nautobot/extras/templates/extras/plugin_detail.html +26 -21
  276. nautobot/extras/templates/extras/plugins_list.html +16 -26
  277. nautobot/extras/templates/extras/role_retrieve.html +64 -0
  278. nautobot/extras/templates/extras/scheduledjob.html +4 -2
  279. nautobot/extras/templates/extras/secretsgroup.html +1 -1
  280. nautobot/extras/templates/extras/tag.html +1 -1
  281. nautobot/extras/templatetags/custom_links.py +12 -12
  282. nautobot/extras/templatetags/job_buttons.py +14 -12
  283. nautobot/extras/test_jobs/invalid_import.py +9 -0
  284. nautobot/extras/test_jobs/log_counts_by_level.py +23 -0
  285. nautobot/extras/test_jobs/missing_import.py +11 -0
  286. nautobot/extras/tests/integration/test_configcontextschema.py +27 -26
  287. nautobot/extras/tests/integration/test_customfields.py +8 -7
  288. nautobot/extras/tests/integration/test_dynamicgroups.py +5 -1
  289. nautobot/extras/tests/integration/test_plugin_banner.py +3 -0
  290. nautobot/extras/tests/integration/test_plugins.py +18 -6
  291. nautobot/extras/tests/test_api.py +27 -18
  292. nautobot/extras/tests/test_approvals.py +38 -38
  293. nautobot/extras/tests/test_changelog.py +35 -3
  294. nautobot/extras/tests/test_customfields.py +22 -13
  295. nautobot/extras/tests/test_customfields_filters.py +479 -0
  296. nautobot/extras/tests/test_dynamicgroups.py +39 -1
  297. nautobot/extras/tests/test_filters.py +21 -19
  298. nautobot/extras/tests/test_forms.py +18 -21
  299. nautobot/extras/tests/test_jobs.py +25 -4
  300. nautobot/extras/tests/test_migrations.py +1 -0
  301. nautobot/extras/tests/test_models.py +13 -31
  302. nautobot/extras/tests/test_plugins.py +36 -10
  303. nautobot/extras/tests/test_views.py +31 -30
  304. nautobot/extras/views.py +81 -19
  305. nautobot/ipam/factory.py +7 -0
  306. nautobot/ipam/filter_mixins.py +38 -0
  307. nautobot/ipam/filters.py +27 -38
  308. nautobot/ipam/formfields.py +1 -1
  309. nautobot/ipam/forms.py +6 -3
  310. nautobot/ipam/migrations/0030_ipam__namespaces.py +13 -0
  311. nautobot/ipam/migrations/0031_ipam___data_migrations.py +4 -1
  312. nautobot/ipam/migrations/0054_namespace_tenant.py +25 -0
  313. nautobot/ipam/models.py +29 -2
  314. nautobot/ipam/navigation.py +3 -2
  315. nautobot/ipam/signals.py +71 -0
  316. nautobot/ipam/tables.py +13 -6
  317. nautobot/ipam/templates/ipam/inc/toggle_available.html +10 -10
  318. nautobot/ipam/templates/ipam/inc/vlangroup_header.html +1 -0
  319. nautobot/ipam/templates/ipam/ipaddress.html +14 -0
  320. nautobot/ipam/templates/ipam/ipaddress_merge.html +3 -3
  321. nautobot/ipam/templates/ipam/ipaddresstointerface_retrieve.html +1 -0
  322. nautobot/ipam/templates/ipam/namespace_update.html +15 -0
  323. nautobot/ipam/templates/ipam/prefix_delete.html +1 -1
  324. nautobot/ipam/templates/ipam/prefix_list.html +14 -13
  325. nautobot/ipam/templates/ipam/service.html +1 -1
  326. nautobot/ipam/templates/ipam/vlan.html +1 -1
  327. nautobot/ipam/templates/ipam/vlan_interfaces.html +1 -1
  328. nautobot/ipam/templates/ipam/vlan_vminterfaces.html +1 -1
  329. nautobot/ipam/tests/migration/test_migrations.py +89 -0
  330. nautobot/ipam/tests/test_api.py +13 -6
  331. nautobot/ipam/tests/test_filters.py +10 -0
  332. nautobot/ipam/tests/test_forms.py +1 -1
  333. nautobot/ipam/tests/test_models.py +43 -1
  334. nautobot/ipam/tests/test_tables.py +1 -2
  335. nautobot/ipam/tests/test_utils.py +1 -1
  336. nautobot/ipam/tests/test_views.py +13 -14
  337. nautobot/ipam/ui.py +0 -17
  338. nautobot/ipam/utils/migrations.py +16 -2
  339. nautobot/ipam/utils/testing.py +9 -3
  340. nautobot/ipam/views.py +46 -6
  341. nautobot/project-static/dist/css/nautobot.css +1 -1
  342. nautobot/project-static/dist/css/nautobot.css.map +1 -1
  343. nautobot/project-static/dist/js/nautobot.js +1 -1
  344. nautobot/project-static/dist/js/nautobot.js.map +1 -1
  345. nautobot/project-static/js/cabletrace.js +1 -1
  346. nautobot/project-static/js/interface_filtering.js +20 -16
  347. nautobot/project-static/nautobot-icons/battery-3.svg +3 -0
  348. nautobot/project-static/nautobot-icons/cloud.svg +1 -1
  349. nautobot/project-static/nautobot-icons/control-panel.svg +1 -1
  350. nautobot/project-static/nautobot-icons/device-lifecycle.svg +1 -1
  351. nautobot/project-static/nautobot-icons/elements.svg +1 -1
  352. nautobot/project-static/nautobot-icons/extensibility.svg +3 -0
  353. nautobot/project-static/nautobot-icons/hammer.svg +1 -1
  354. nautobot/project-static/nautobot-icons/organization.svg +3 -0
  355. nautobot/project-static/nautobot-icons/secrets.svg +1 -1
  356. nautobot/project-static/nautobot-icons/security.svg +3 -0
  357. nautobot/project-static/nautobot-icons/server.svg +1 -1
  358. nautobot/project-static/nautobot-icons/star-filled.svg +1 -1
  359. nautobot/project-static/nautobot-icons/star.svg +1 -1
  360. nautobot/tenancy/api/serializers.py +1 -0
  361. nautobot/tenancy/api/views.py +2 -1
  362. nautobot/tenancy/{filters/__init__.py → filters.py} +2 -10
  363. nautobot/tenancy/navigation.py +3 -1
  364. nautobot/tenancy/tests/test_filters.py +0 -2
  365. nautobot/tenancy/views.py +2 -1
  366. nautobot/ui/src/js/collapse.js +3 -3
  367. nautobot/ui/src/js/nautobot.js +16 -0
  368. nautobot/ui/src/scss/colors.scss +1 -1
  369. nautobot/ui/src/scss/nautobot.scss +61 -28
  370. nautobot/users/templates/users/profile.html +45 -12
  371. nautobot/users/templates/users/sessionkey_delete.html +1 -1
  372. nautobot/users/tests/test_api.py +4 -0
  373. nautobot/users/views.py +4 -2
  374. nautobot/virtualization/models.py +1 -68
  375. nautobot/virtualization/navigation.py +3 -2
  376. nautobot/virtualization/templates/virtualization/virtual_machine_vminterface_delete.html +1 -1
  377. nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
  378. nautobot/virtualization/templates/virtualization/virtualmachine_list.html +2 -2
  379. nautobot/virtualization/templates/virtualization/virtualmachine_update.html +3 -1
  380. nautobot/virtualization/tests/test_api.py +3 -0
  381. nautobot/virtualization/tests/test_models.py +44 -4
  382. nautobot/vpn/__init__.py +0 -0
  383. nautobot/vpn/api/serializers.py +113 -0
  384. nautobot/vpn/api/urls.py +19 -0
  385. nautobot/vpn/api/views.py +70 -0
  386. nautobot/vpn/apps.py +8 -0
  387. nautobot/vpn/choices.py +171 -0
  388. nautobot/vpn/factory.py +209 -0
  389. nautobot/vpn/filters.py +233 -0
  390. nautobot/vpn/forms.py +486 -0
  391. nautobot/vpn/homepage.py +19 -0
  392. nautobot/vpn/migrations/0001_initial.py +541 -0
  393. nautobot/vpn/migrations/0002_populate_defaults.py +199 -0
  394. nautobot/vpn/migrations/__init__.py +0 -0
  395. nautobot/vpn/models.py +527 -0
  396. nautobot/vpn/navigation.py +98 -0
  397. nautobot/vpn/tables.py +380 -0
  398. nautobot/vpn/templates/vpn/vpnprofile.html +2 -0
  399. nautobot/vpn/templates/vpn/vpnprofile_create.html +150 -0
  400. nautobot/vpn/tests/__init__.py +0 -0
  401. nautobot/vpn/tests/test_api.py +341 -0
  402. nautobot/vpn/tests/test_filters.py +139 -0
  403. nautobot/vpn/tests/test_forms.py +294 -0
  404. nautobot/vpn/tests/test_models.py +97 -0
  405. nautobot/vpn/tests/test_views.py +281 -0
  406. nautobot/vpn/urls.py +16 -0
  407. nautobot/vpn/views.py +437 -0
  408. nautobot/wireless/navigation.py +3 -2
  409. nautobot/wireless/tests/integration/test_radio_profile.py +1 -5
  410. nautobot/wireless/tests/test_api.py +1 -1
  411. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/METADATA +14 -14
  412. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/RECORD +417 -366
  413. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/entry_points.txt +1 -0
  414. nautobot/data_validation/template_content.py +0 -42
  415. nautobot/dcim/filters/mixins.py +0 -354
  416. nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +0 -4
  417. /nautobot/tenancy/{filters/mixins.py → filter_mixins.py} +0 -0
  418. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/LICENSE.txt +0 -0
  419. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/NOTICE +0 -0
  420. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/WHEEL +0 -0
@@ -9,7 +9,7 @@
9
9
  {% render_field form.name %}
10
10
  {% render_field form.model_content_type %}
11
11
  {% render_field form.model_constraints %}
12
- {% render_field form.priority %}
12
+ {% render_field form.weight %}
13
13
  </div>
14
14
  </div>
15
15
  <div class="card">
@@ -22,7 +22,9 @@
22
22
  <thead>
23
23
  <tr>
24
24
  {% for field in stage.visible_fields %}
25
- <th>{{ field.label|capfirst }}</th>
25
+ <th>
26
+ <span data-bs-toggle="tooltip" data-bs-placement="top" title="{{ field.help_text }}">{{ field.label|capfirst }}</span>
27
+ </th>
26
28
  {% endfor %}
27
29
  </tr>
28
30
  </thead>
@@ -3,19 +3,27 @@
3
3
 
4
4
  {% block buttons %}
5
5
  <span title="{% if not object.is_active_stage %}This stage is not active{% elif request.user in object.users_that_already_approved %}You already approved this stage{% else %}Approve this stage{% endif %}">
6
- <a
7
- href="{% url 'extras:approvalworkflowstage_approve' object.pk %}?return_url={{request.path}}"
8
- class="btn btn-success{% if not object.is_active_stage or request.user in object.users_that_already_approved %} disabled{% endif %}"
9
- >
10
- <span class="mdi mdi-check"></span> Approve
11
- </a>
6
+ {% if object.is_active_stage and request.user not in object.users_that_already_approved %}
7
+ <a href="{% url 'extras:approvalworkflowstage_approve' object.pk %}?return_url={{request.path}}"
8
+ class="btn btn-success">
9
+ <span class="mdi mdi-check"></span> Approve
10
+ </a>
11
+ {% else %}
12
+ <a aria-disabled="true" class="btn btn-success disabled">
13
+ <span class="mdi mdi-check"></span> Approve
14
+ </a>
15
+ {% endif %}
12
16
  </span>
13
17
  <span title="{% if not object.is_active_stage %}This stage is not active{% elif request.user in object.users_that_already_approved %}You already approved this stage{% else %}Deny this stage{% endif %}">
14
- <a
15
- href="{% url 'extras:approvalworkflowstage_deny' object.pk %}?return_url={{request.path}}"
16
- class="btn btn-danger {% if not object.is_active_stage or request.user in object.users_that_already_approved %} disabled{% endif %}"
17
- >
18
- <span class="mdi mdi-close"></span> Deny
19
- </a>
18
+ {% if object.is_active_stage and request.user not in object.users_that_already_approved %}
19
+ <a href="{% url 'extras:approvalworkflowstage_deny' object.pk %}?return_url={{request.path}}"
20
+ class="btn btn-danger">
21
+ <span class="mdi mdi-close"></span> Deny
22
+ </a>
23
+ {% else %}
24
+ <a aria-disabled="true" class="btn btn-danger disabled">
25
+ <span class="mdi mdi-close"></span> Deny
26
+ </a>
27
+ {% endif %}
20
28
  </span>
21
29
  {% endblock buttons %}
@@ -1,2 +1,2 @@
1
- {% extends 'extras/computedfield_retrieve.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% comment %}3.0 TODO: remove this template, which only exists for backward compatibility with 2.4 and earlier{% endcomment %}
@@ -1,2 +1,2 @@
1
- {% extends 'extras/configcontext_retrieve.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% comment %}3.0 TODO: remove this template, which only exists for backward compatibility with 2.4 and earlier{% endcomment %}
@@ -1,6 +1,6 @@
1
- {% extends 'extras/configcontextschema.html' %}
1
+ {% extends 'extras/configcontextschema_retrieve.html' %}
2
2
 
3
- <h1>{% block title %}{{ object }} - Validation{% endblock %}</h1>
3
+ {% block title %}{{ object }} - Validation{% endblock %}
4
4
 
5
5
  {% block content %}
6
6
  <div class="row">
@@ -1,2 +1,2 @@
1
- {% extends 'extras/customfield_retrieve.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% comment %}3.0 TODO: remove this template, which only exists for backward compatibility with 2.4 and earlier{% endcomment %}
@@ -11,15 +11,21 @@
11
11
 
12
12
  {% block extra_tab_content %}
13
13
  <div id="members" role="tabpanel" class="tab-pane {% if not active_tab and not request.GET.tab or request.GET.tab == "members" %}active{% else %}fade{% endif %}">
14
- {% if members_list_url %}
15
- <div class="row">
16
- <div class="col-lg-12">
14
+ <div class="row">
15
+ <div class="col-lg-12">
16
+ {% if members_list_url %}
17
17
  <div class="alert alert-success" role="alert">
18
18
  You can bulk-add and bulk-remove members of this group from the <a href="{{ members_list_url }}">{{ members_verbose_name_plural|bettertitle }} list view</a>.
19
19
  </div>
20
- </div>
20
+ {% else %}
21
+ <div class="alert alert-warning" role="alert">
22
+ Dynamic group membership is cached for performance reasons, therefore this listing may not always
23
+ be up-to-date.<br>You can refresh the membership of this group asynchronously by clicking the
24
+ "Refresh Members" button above.
25
+ </div>
26
+ {% endif %}
21
27
  </div>
22
- {% endif %}
28
+ </div>
23
29
  <div class="row">
24
30
  <div class="col-lg-12">
25
31
  {% include 'utilities/obj_table.html' with table=members_table table_template='panel_table.html' heading='Dynamic Group members' %}
@@ -43,7 +43,7 @@
43
43
  <div class="tab-content">
44
44
  <div class="tab-pane active" id="filter-form">
45
45
  {% if filter_form %}
46
- <span class="form-text">
46
+ <span class="form-text py-6">
47
47
  Select the filtering criteria to determine membership of objects matching
48
48
  the Content Type for this Dynamic Group. Fields that are not a dropdown are
49
49
  expected to have string inputs and do not support multiple values.
@@ -3,8 +3,6 @@
3
3
  {% load log_levels %}
4
4
  {% load static %}
5
5
 
6
- {% block title %}{{ block.super }} - Synchronization Status{% endblock %}
7
-
8
6
  {% block content %}
9
7
  <div class="row">
10
8
  <div class="col-lg-12">
@@ -1,97 +1,2 @@
1
1
  {% extends 'generic/object_retrieve.html' %}
2
- {% load helpers %}
3
-
4
- {% block content_left_page %}
5
- <div class="card">
6
- <div class="card-header">
7
- <strong>Query</strong>
8
- </div>
9
- <table class="table table-hover card-body attr-table">
10
- <tr>
11
- <td>Name</td>
12
- <td><span>{{ object.name }}</span></td>
13
- </tr>
14
- <tr>
15
- <td>Query</td>
16
- <td><pre id="query"><code class="language-graphql">{{ object.query }}</code></pre></td>
17
- </tr>
18
- <tr>
19
- <td>Query Variables</td>
20
- <td><pre>{{ object.variables|render_json }}</pre></td>
21
- </tr>
22
- </table>
23
- </div>
24
- {% endblock content_left_page %}
25
-
26
- {% block content_right_page %}
27
- <div class="card">
28
- <div class="card-header">
29
- <strong>Response</strong>
30
- <button class="btn btn-primary btn-sm float-end" onclick="test_query()">Execute</button>
31
- </div>
32
- {% if object.variables %}
33
- <table class="table table-hover card-body attr-table">
34
- <tr>
35
- <td>Variables:</td>
36
- <td><textarea id="query_variables" class="form-control">{{ object.variables|render_json:False }}</textarea></td>
37
- </tr>
38
- </table>
39
- {% endif %}
40
- <div class="card-footer">
41
- <pre id="query_output">
42
-
43
- </pre>
44
- </div>
45
- </div>
46
- {% endblock content_right_page %}
47
-
48
- {% block javascript %}
49
- {{ block.super }}
50
- <script>
51
- function fillQueryOutput(data) {
52
- const queryOutput = document.querySelector('#query_output');
53
-
54
- [...queryOutput.childNodes].forEach(childNode => childNode.remove());
55
-
56
- const code = document.createElement('code');
57
- code.classList.add('language-json');
58
- code.textContent = JSON.stringify(data, undefined, 2);
59
-
60
- queryOutput.appendChild(code);
61
-
62
- hljs.highlightElement(code);
63
- }
64
-
65
- function test_query() {
66
- const variables = document.querySelector('#query_variables')?.value;
67
-
68
- $.ajax({
69
- url: "{% url 'graphql' %}",
70
- method: "POST",
71
- headers: {"X-CSRFTOKEN": "{{ csrf_token }}"},
72
- dataType: "json",
73
- data: {
74
- "query": `{{ object.query | escapejs }}`,
75
- "variables": variables,
76
- },
77
- success: function(data) {
78
- fillQueryOutput(data);
79
- },
80
- error: function(error) {
81
- console.log(error);
82
- fillQueryOutput(error.responseJSON);
83
- }
84
- });
85
- };
86
-
87
- textarea = document.querySelector("#query_variables");
88
- textarea.addEventListener('input', autoResize, false);
89
- textarea.style.height = 'auto';
90
- textarea.style.height = textarea.scrollHeight + 'px';
91
-
92
- function autoResize() {
93
- this.style.height = 'auto';
94
- this.style.height = this.scrollHeight + 'px';
95
- }
96
- </script>
97
- {% endblock javascript %}
2
+ {% comment %}3.0 TODO: remove this template, which only exists for backward compatibility with 2.4 and earlier{% endcomment %}
@@ -10,15 +10,29 @@
10
10
  {% endif %}
11
11
  {% if "approve" in buttons and have_permission and record.is_active_stage %}
12
12
  <span title='{% if request.user in record.users_that_already_approved %}You already approved this stage{% else %}Approve this stage{% endif %}'>
13
- <a href="{% url approval_route pk=record.pk %}?return_url={{ request.path }}{{ return_url_extra }}" class="btn btn-success btn-sm{% if request.user in record.users_that_already_approved %} disabled{% endif %}">
14
- <span class="mdi mdi-check"></span>
15
- </a>
13
+ {% if request.user not in record.users_that_already_approved %}
14
+ <a href="{% url approval_route pk=record.pk %}?return_url={{ request.path }}{{ return_url_extra }}"
15
+ class="btn btn-success btn-sm">
16
+ <span class="mdi mdi-check"></span>
17
+ </a>
18
+ {% else %}
19
+ <a aria-disabled="true" class="btn btn-success btn-sm disabled">
20
+ <span class="mdi mdi-check"></span>
21
+ </a>
22
+ {% endif %}
16
23
  </span>
17
24
  {% endif %}
18
25
  {% if "deny" in buttons and have_permission and record.is_active_stage and request.user not in record.users_that_already_approved %}
19
26
  <span title='{%if request.user in record.users_that_already_approved %}You already approved this stage{% else %}Deny this stage{% endif %}'>
20
- <a href="{% url deny_route pk=record.pk %}?return_url={{ request.path }}{{ return_url_extra }}" class="btn btn-sm btn-danger {% if request.user in record.users_that_already_approved %} disabled{% endif %}">
21
- <span class="mdi mdi-close"></span>
22
- </a>
27
+ {% if request.user not in record.users_that_already_approved %}
28
+ <a href="{% url deny_route pk=record.pk %}?return_url={{ request.path }}{{ return_url_extra }}"
29
+ class="btn btn-danger btn-sm">
30
+ <span class="mdi mdi-close"></span>
31
+ </a>
32
+ {% else %}
33
+ <a aria-disabled="true" class="btn btn-danger btn-sm disabled">
34
+ <span class="mdi mdi-close"></span>
35
+ </a>
36
+ {% endif %}
23
37
  </span>
24
38
  {% endif %}
@@ -1,7 +1,8 @@
1
1
  {% load form_helpers %}
2
2
 
3
- <hr>
4
- <div class="mb-10 d-flex justify-content-center{% if field.errors %} has-error{% endif %}">
3
+
4
+ <hr class="border-top">
5
+ <div class="mb-10 d-lg-flex justify-content-center{% if field.errors %} has-error{% endif %}">
5
6
  <label class="col-lg-3 col-form-label{% if field.field.required %} nb-required{% endif %}" for="{{ field.id_for_label }}">
6
7
  {{ field.label }}
7
8
  </label>
@@ -19,17 +20,17 @@
19
20
  {% endif %}
20
21
  </div>
21
22
  </div>
22
- <div class="mb-10 d-flex justify-content-center">
23
+ <div class="mb-10 d-lg-flex justify-content-center">
23
24
  <span class="col-lg-3 col-form-label">
24
25
  </span>
25
26
  <div class="col-lg-9">
26
- <div>
27
- <label for="{{ override_field.id_for_label }}">
28
- {{ override_field }}
27
+ <div class="form-check flex-wrap">
28
+ {{ override_field }}
29
+ <label class="form-check-label" for="{{ override_field.id_for_label }}">
29
30
  <i>Clear override</i>
30
31
  </label>
31
32
  {% if override_field.help_text %}
32
- <span class="form-text">{{ override_field.help_text|safe }}</span>
33
+ <span class="form-text w-100">{{ override_field.help_text|safe }}</span>
33
34
  {% endif %}
34
35
  </div>
35
36
  </div>
@@ -1,6 +1,13 @@
1
+ {% load helpers %}
1
2
  <div class="float-end">
2
- <div class="btn-group btn-group-xs" role="group">
3
- <a href="?data_format=json" class="btn {% if format == 'json' %}btn-primary{% else %}btn-secondary{% endif %}">JSON</a>
4
- <a href="?data_format=yaml" class="btn {% if format == 'yaml' %}btn-primary{% else %}btn-secondary{% endif %}">YAML</a>
3
+ <div class="btn-group btn-group-sm" role="group">
4
+ <a href="{% django_querystring data_format='json' %}"
5
+ class="btn btn-primary {% if data_format == 'json' %}bg-primary nb-text-body-bg{% endif %}">
6
+ <span class="mdi mdi-code-json" aria-hidden="true"></span> JSON
7
+ </a>
8
+ <a href="{% django_querystring data_format='yaml' %}"
9
+ class="btn btn-primary {% if data_format == 'yaml' %}bg-primary nb-text-body-bg{% endif %}">
10
+ <span class="mdi mdi-format-list-group" aria-hidden="true"></span> YAML
11
+ </a>
5
12
  </div>
6
13
  </div>
@@ -0,0 +1,71 @@
1
+ {% load helpers %}
2
+
3
+ <div class="card">
4
+ <div class="card-header">
5
+ <strong>Response</strong>
6
+ <button class="btn btn-primary btn-sm float-end" onclick="test_query()">Execute</button>
7
+ </div>
8
+ {% if object.variables %}
9
+ <table class="table table-hover card-body attr-table">
10
+ <tr>
11
+ <td>Variables:</td>
12
+ <td><textarea id="query_variables" class="form-control">{{ object.variables|render_json:False }}</textarea></td>
13
+ </tr>
14
+ </table>
15
+ {% endif %}
16
+ <div class="card-footer">
17
+ <pre id="query_output" class="p-5">
18
+
19
+ </pre>
20
+ </div>
21
+ </div>
22
+
23
+ <script>
24
+ function fillQueryOutput(data) {
25
+ const queryOutput = document.querySelector('#query_output');
26
+
27
+ [...queryOutput.childNodes].forEach(childNode => childNode.remove());
28
+
29
+ const code = document.createElement('code');
30
+ code.classList.add('language-json');
31
+ code.textContent = JSON.stringify(data, undefined, 2);
32
+
33
+ queryOutput.appendChild(code);
34
+
35
+ hljs.highlightElement(code);
36
+ }
37
+
38
+ function test_query() {
39
+ const variables = document.querySelector('#query_variables')?.value;
40
+
41
+ $.ajax({
42
+ url: "{% url 'graphql' %}",
43
+ method: "POST",
44
+ headers: {"X-CSRFTOKEN": "{{ csrf_token }}"},
45
+ dataType: "json",
46
+ data: {
47
+ "query": `{{ object.query | escapejs }}`,
48
+ "variables": variables,
49
+ },
50
+ success: function(data) {
51
+ fillQueryOutput(data);
52
+ },
53
+ error: function(error) {
54
+ console.log(error);
55
+ fillQueryOutput(error.responseJSON);
56
+ }
57
+ });
58
+ };
59
+
60
+ window.addEventListener("load", () => {
61
+ textarea = document.querySelector("#query_variables");
62
+ textarea.addEventListener('input', autoResize, false);
63
+ textarea.style.height = 'auto';
64
+ textarea.style.height = textarea.scrollHeight + 'px';
65
+ });
66
+
67
+ function autoResize() {
68
+ this.style.height = 'auto';
69
+ this.style.height = this.scrollHeight + 'px';
70
+ }
71
+ </script>
@@ -19,9 +19,21 @@
19
19
  <header class="nb-tile-header">
20
20
  <h3>{{ row.record.grouping|bettertitle }}</h3>
21
21
 
22
- <a href="{% url 'extras:job_run' pk=row.record.pk %}" class="btn btn-primary btn-sm" data-bs-toggle="tooltip" data-bs-title="Run/Schedule" {% if not perms.extras.run_job or not row.record.runnable %}disabled="disabled"{% endif %}>
23
- <span class="mdi mdi-play" aria-hidden="true"><span class="visually-hidden">Run/Schedule</span></span>
24
- </a>
22
+ {% if perms.extras.run_job and row.record.runnable %}
23
+ <a href="{% url 'extras:job_run' pk=row.record.pk %}"
24
+ class="btn btn-primary btn-sm"
25
+ data-bs-toggle="tooltip"
26
+ data-bs-title="Run/Schedule">
27
+ <span class="mdi mdi-play" aria-hidden="true"><span class="visually-hidden">Run/Schedule</span></span>
28
+ </a>
29
+ {% else %}
30
+ <a aria-disabled="true"
31
+ class="btn btn-primary btn-sm disabled"
32
+ data-bs-toggle="tooltip"
33
+ data-bs-title="Run/Schedule">
34
+ <span class="mdi mdi-play" aria-hidden="true"><span class="visually-hidden">Run/Schedule</span></span>
35
+ </a>
36
+ {% endif %}
25
37
 
26
38
  {% comment "TODO(norbert-mieczkowski-codilime): use the same buttons (actions) in tables and tiles when tables support dropdown menus" %}
27
39
  {% for item in row.items %}
@@ -1,6 +1,13 @@
1
+ {% load helpers %}
1
2
  <div class="float-end">
2
- <div class="btn-group btn-group-xs" role="group">
3
- <a href="?format=json" class="btn btn-secondary{% if format == 'json' %} active{% endif %}">JSON</a>
4
- <a href="?format=yaml" class="btn btn-secondary{% if format == 'yaml' %} active{% endif %}">YAML</a>
3
+ <div class="btn-group btn-group-sm" role="group">
4
+ <a href="{% django_querystring format='json' %}"
5
+ class="btn btn-primary{% if format == 'json' %} bg-primary nb-text-body-bg{% endif %}">
6
+ <span class="mdi mdi-code-json" aria-hidden="true"></span> JSON
7
+ </a>
8
+ <a href="{% django_querystring format='yaml' %}"
9
+ class="btn btn-primary{% if format == 'yaml' %} bg-primary nb-text-body-bg{% endif %}">
10
+ <span class="mdi mdi-format-list-group" aria-hidden="true"></span> YAML
11
+ </a>
5
12
  </div>
6
13
  </div>
@@ -1,27 +1,28 @@
1
1
  {% load form_helpers %}
2
2
 
3
- <div class="mb-10 d-flex justify-content-center{% if field.errors %} has-error{% endif %}">
3
+ <div class="mb-10 d-lg-flex justify-content-center{% if field.errors %} has-error{% endif %}">
4
4
  <label class="col-lg-3 col-form-label{% if field.field.required %} nb-required{% endif %}" for="{{ field.id_for_label }}">
5
5
  {{ field.label }}
6
6
  </label>
7
- <div class="col-lg-3">
8
- <div class="form-check{% if override_field.errors %} has-error{% endif %}">
9
- <label for="{{ override_field.id_for_label }}">
10
- {{ override_field }}
7
+ <div class="col-lg-3 pe-lg-10">
8
+ <div class="form-check my-6{% if override_field.errors %} has-error{% endif %}">
9
+ {{ override_field }}
10
+ <label class="form-check-label" for="{{ override_field.id_for_label }}">
11
11
  <i>Override default value</i>
12
+ {% if override_field.help_text %}
13
+ <span class="form-text">{{ override_field.help_text|safe }}</span>
14
+ {% endif %}
12
15
  </label>
13
- {% if override_field.help_text %}
14
- <span class="form-text">{{ override_field.help_text|safe }}</span>
15
- {% endif %}
16
16
  </div>
17
17
  </div>
18
18
  <div class="col-lg-6">
19
19
  {% if field|widget_type == 'checkboxinput' %}
20
- <div class="form-check{% if field.errors %} has-error{% endif %}">
21
- <label for="{{ field.id_for_label }}">
22
- {{ field }} {{ field.label }}
20
+ <div class="form-check my-6{% if field.errors %} has-error{% endif %}">
21
+ {{ field }}
22
+ <label class="form-check-label" for="{{ field.id_for_label }}">
23
+ {{ field.label }}
24
+ {% if field.help_text %}<span class="form-text">{{ field.help_text|safe }}</span>{% endif %}
23
25
  </label>
24
- {% if field.help_text %}<span class="form-text">{{ field.help_text|safe }}</span>{% endif %}
25
26
  </div>
26
27
  {% else %}
27
28
  {{ field }}
@@ -14,17 +14,10 @@
14
14
  <li class="breadcrumb-item">Submission and Scheduling</li>
15
15
  </ol>
16
16
  </div>
17
- <div>
18
- <h1 class="mb-16">{{ job_model }}</h1>
19
- {% if job_model.read_only %}
20
- <span class="badge bg-body-secondary border">Read-only</span>
21
- {% endif %}
22
- <p>{{ job_model.description | render_markdown }}</p>
23
- </div>
24
17
  <form action="" method="post" enctype="multipart/form-data" class="h-100 vstack">
25
18
  {% csrf_token %}
26
- <div class="row align-content-start flex-fill">
27
- <div class="col-xl-8 offset-lg-2 col-lg-10 offset-md-1">
19
+ <div class="row justify-content-center flex-fill">
20
+ <div class="col-xl-8 col-lg-10">
28
21
  {% if not perms.extras.run_job %}
29
22
  <div class="alert alert-warning">
30
23
  <span aria-hidden="true" class="mdi mdi-alert"></span>
@@ -40,6 +33,11 @@
40
33
  <span aria-hidden="true" class="mdi mdi-alert"></span> This job is not enabled to be run.
41
34
  </div>
42
35
  {% endif %}
36
+ {% if job_model.read_only %}
37
+ <div class="alert alert-info">
38
+ <span aria-hidden="true" class="mdi mdi-info"></span>This job is read-only in its operation.
39
+ </div>
40
+ {% endif %}
43
41
  {% if job_form.non_field_errors or schedule_form.non_field_errors %}
44
42
  <div class="card border-danger">
45
43
  <div class="card-header bg-danger-subtle border-danger text-body">
@@ -88,7 +86,7 @@
88
86
  id="id__run"
89
87
  name="_run"
90
88
  type="submit"
91
- {% if not perms.extras.run_job or not job_model.runnable %}disabled="disabled"{% endif %}
89
+ {% if not perms.extras.run_job or not job_model.runnable %}disabled{% endif %}
92
90
  >
93
91
  <span aria-hidden="true" class="mdi mdi-play me-4"></span><!--
94
92
  -->Run Job Now
@@ -147,13 +145,32 @@
147
145
 
148
146
  // Toggle schedule name and schedule time field rows.
149
147
  ['#id__schedule_name', '#id__schedule_start_time'].forEach((selector) => {
150
- const row = document.querySelector(selector).parentElement.parentElement;
148
+ const input = document.querySelector(selector);
149
+ const row = input?.parentElement.parentElement;
151
150
  row.classList.toggle('d-none', immediately);
151
+ const label = row.querySelector('label');
152
+
153
+ label?.classList.toggle('nb-required', !immediately);
154
+ if (immediately) {
155
+ input.removeAttribute('required');
156
+ } else {
157
+ input.setAttribute('required', '');
158
+ }
152
159
  });
153
160
 
154
161
  // Toggle custom crontab field row.
155
- const row = document.querySelector('#id__recurrence_custom_time').parentElement.parentElement;
162
+ input = document.querySelector('#id__recurrence_custom_time')
163
+ const row = input.parentElement.parentElement;
156
164
  row.classList.toggle('d-none', !custom);
165
+ const label = row.querySelector('label');
166
+
167
+ label?.classList.toggle('nb-required', custom);
168
+ if (!custom) {
169
+ input.removeAttribute('required');
170
+ } else {
171
+ input.setAttribute('required', '');
172
+ }
173
+
157
174
  };
158
175
 
159
176
  {% if job_model.has_sensitive_variables %}
@@ -1,6 +1,24 @@
1
1
  {% extends 'generic/object_bulk_update.html' %}
2
2
  {% load form_helpers %}
3
3
 
4
+ {% block extra_styles %}
5
+ <style>
6
+ tr a.job_run {
7
+ display: inline-flex;
8
+ gap: 0.625rem; /* 10px */
9
+ }
10
+
11
+ tr a.job_run .btn {
12
+ align-self: flex-start;
13
+ margin-block: -0.09375rem; /* -1.5px */
14
+ }
15
+
16
+ tr :last-child {
17
+ margin-bottom: 0;
18
+ }
19
+ </style>
20
+ {% endblock %}
21
+
4
22
  {% block form_fields %}
5
23
  {% render_field form.enabled %}
6
24
  {% include "extras/inc/bulk_edit_overridable_field.html" with field=form.grouping override_field=form.clear_grouping_override %}