nautobot 3.0.0rc1__py3-none-any.whl → 3.0.1__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 (103) hide show
  1. nautobot/apps/forms.py +8 -0
  2. nautobot/apps/templatetags.py +231 -0
  3. nautobot/apps/testing.py +11 -1
  4. nautobot/apps/ui.py +21 -1
  5. nautobot/apps/utils.py +26 -1
  6. nautobot/core/celery/__init__.py +46 -1
  7. nautobot/core/cli/bootstrap_v3_to_v5.py +185 -44
  8. nautobot/core/cli/bootstrap_v3_to_v5_changes.yaml +314 -0
  9. nautobot/core/graphql/generators.py +2 -2
  10. nautobot/core/jobs/bulk_actions.py +12 -6
  11. nautobot/core/jobs/cleanup.py +13 -1
  12. nautobot/core/settings.py +13 -0
  13. nautobot/core/settings.yaml +22 -0
  14. nautobot/core/settings_funcs.py +11 -1
  15. nautobot/core/tables.py +19 -1
  16. nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
  17. nautobot/core/templates/components/panel/header_extra_content_table.html +9 -3
  18. nautobot/core/templates/generic/object_create.html +1 -1
  19. nautobot/core/templates/inc/header.html +9 -10
  20. nautobot/core/templates/login.html +16 -1
  21. nautobot/core/templates/nautobot_config.py.j2 +14 -1
  22. nautobot/core/templates/redoc_ui.html +3 -0
  23. nautobot/core/templatetags/helpers.py +3 -3
  24. nautobot/core/testing/views.py +3 -1
  25. nautobot/core/tests/test_graphql.py +13 -0
  26. nautobot/core/tests/test_jobs.py +118 -0
  27. nautobot/core/tests/test_views.py +24 -0
  28. nautobot/core/ui/bulk_buttons.py +2 -3
  29. nautobot/core/utils/lookup.py +2 -3
  30. nautobot/core/utils/permissions.py +1 -1
  31. nautobot/core/views/generic.py +1 -0
  32. nautobot/core/views/mixins.py +37 -10
  33. nautobot/core/views/renderers.py +1 -0
  34. nautobot/core/views/utils.py +3 -3
  35. nautobot/data_validation/views.py +1 -9
  36. nautobot/dcim/forms.py +9 -9
  37. nautobot/dcim/models/devices.py +3 -3
  38. nautobot/dcim/tables/power.py +3 -0
  39. nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +1 -1
  40. nautobot/dcim/views.py +30 -44
  41. nautobot/extras/api/views.py +14 -3
  42. nautobot/extras/choices.py +3 -0
  43. nautobot/extras/jobs.py +48 -2
  44. nautobot/extras/migrations/0132_approval_workflow_seed_data.py +127 -0
  45. nautobot/extras/models/approvals.py +11 -1
  46. nautobot/extras/models/models.py +19 -0
  47. nautobot/extras/models/relationships.py +3 -1
  48. nautobot/extras/tables.py +35 -18
  49. nautobot/extras/templates/extras/approval_workflow/approve.html +9 -2
  50. nautobot/extras/templates/extras/approval_workflow/deny.html +9 -3
  51. nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +1 -1
  52. nautobot/extras/templates/extras/customfield_update.html +1 -1
  53. nautobot/extras/templates/extras/dynamicgroup_update.html +2 -2
  54. nautobot/extras/templates/extras/inc/approval_buttons_column.html +10 -2
  55. nautobot/extras/templates/extras/inc/job_tiles.html +2 -2
  56. nautobot/extras/templates/extras/inc/jobresult.html +1 -1
  57. nautobot/extras/templates/extras/metadatatype_create.html +1 -1
  58. nautobot/extras/templates/extras/object_approvalworkflow.html +2 -3
  59. nautobot/extras/templates/extras/secretsgroup_update.html +1 -1
  60. nautobot/extras/tests/test_api.py +57 -3
  61. nautobot/extras/tests/test_customfields_filters.py +84 -4
  62. nautobot/extras/tests/test_views.py +323 -6
  63. nautobot/extras/views.py +114 -39
  64. nautobot/ipam/constants.py +2 -2
  65. nautobot/ipam/tables.py +7 -6
  66. nautobot/load_balancers/constants.py +6 -0
  67. nautobot/load_balancers/migrations/0001_initial.py +14 -3
  68. nautobot/load_balancers/models.py +5 -4
  69. nautobot/load_balancers/tables.py +5 -0
  70. nautobot/project-static/dist/css/nautobot.css +1 -1
  71. nautobot/project-static/dist/css/nautobot.css.map +1 -1
  72. nautobot/project-static/dist/js/graphql-libraries.js +1 -1
  73. nautobot/project-static/dist/js/graphql-libraries.js.map +1 -1
  74. nautobot/project-static/dist/js/libraries.js +1 -1
  75. nautobot/project-static/dist/js/libraries.js.LICENSE.txt +38 -2
  76. nautobot/project-static/dist/js/libraries.js.map +1 -1
  77. nautobot/project-static/dist/js/nautobot-graphiql.js +1 -1
  78. nautobot/project-static/dist/js/nautobot-graphiql.js.map +1 -1
  79. nautobot/project-static/dist/js/nautobot.js +1 -1
  80. nautobot/project-static/dist/js/nautobot.js.map +1 -1
  81. nautobot/project-static/img/dark-theme.png +0 -0
  82. nautobot/project-static/img/light-theme.png +0 -0
  83. nautobot/project-static/img/system-theme.png +0 -0
  84. nautobot/project-static/js/forms.js +1 -85
  85. nautobot/tenancy/tables.py +3 -2
  86. nautobot/tenancy/views.py +3 -2
  87. nautobot/ui/package-lock.json +553 -569
  88. nautobot/ui/package.json +10 -10
  89. nautobot/ui/src/js/checkbox.js +132 -0
  90. nautobot/ui/src/js/nautobot.js +6 -0
  91. nautobot/ui/src/js/select2.js +69 -73
  92. nautobot/ui/src/js/theme.js +129 -39
  93. nautobot/ui/src/scss/nautobot.scss +11 -1
  94. nautobot/vpn/templates/vpn/vpnprofile_create.html +2 -2
  95. nautobot/wireless/filters.py +15 -1
  96. nautobot/wireless/tables.py +18 -14
  97. nautobot/wireless/templates/wireless/wirelessnetwork_create.html +1 -1
  98. {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/METADATA +2 -2
  99. {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/RECORD +103 -98
  100. {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/LICENSE.txt +0 -0
  101. {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/NOTICE +0 -0
  102. {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/WHEEL +0 -0
  103. {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,127 @@
1
+ # Generated by Django 4.2.25 on 2025-11-11 04:24
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ def create_default_scheduledjob_workflow(apps, schema_editor):
7
+ Group = apps.get_model("auth", "Group")
8
+ ObjectPermission = apps.get_model("users", "ObjectPermission")
9
+ ApprovalWorkflow = apps.get_model("extras", "ApprovalWorkflow")
10
+ ApprovalWorkflowStage = apps.get_model("extras", "ApprovalWorkflowStage")
11
+ ApprovalWorkflowStageResponse = apps.get_model("extras", "ApprovalWorkflowStageResponse")
12
+ ApprovalWorkflowDefinition = apps.get_model("extras", "ApprovalWorkflowDefinition")
13
+ ApprovalWorkflowStageDefinition = apps.get_model("extras", "ApprovalWorkflowStageDefinition")
14
+ ContentType = apps.get_model("contenttypes", "ContentType")
15
+ ScheduledJob = apps.get_model("extras", "ScheduledJob")
16
+
17
+ # --- Groups ---
18
+ groups = {
19
+ "nautobot-default-scheduledjob-approver": Group.objects.get_or_create(
20
+ name="nautobot-default-scheduledjob-approver"
21
+ )[0],
22
+ "nautobot-default-scheduledjob-operator": Group.objects.get_or_create(
23
+ name="nautobot-default-scheduledjob-operator"
24
+ )[0],
25
+ "nautobot-default-scheduledjob-architect": Group.objects.get_or_create(
26
+ name="nautobot-default-scheduledjob-architect"
27
+ )[0],
28
+ }
29
+
30
+ # --- Content Types ---
31
+ ct_workflow = ContentType.objects.get_for_model(ApprovalWorkflow)
32
+ ct_stage = ContentType.objects.get_for_model(ApprovalWorkflowStage)
33
+ ct_response = ContentType.objects.get_for_model(ApprovalWorkflowStageResponse)
34
+ ct_workflow_def = ContentType.objects.get_for_model(ApprovalWorkflowDefinition)
35
+ ct_stage_def = ContentType.objects.get_for_model(ApprovalWorkflowStageDefinition)
36
+ ct_scheduled_job = ContentType.objects.get_for_model(ScheduledJob)
37
+ # --- Approval Workflow Definition ---
38
+ awf_def, _ = ApprovalWorkflowDefinition.objects.update_or_create(
39
+ name="Scheduled Jobs Approval - Example",
40
+ defaults={
41
+ "model_content_type_id": ct_scheduled_job.id,
42
+ "model_constraints": {"job_model__job_class_name": "JobThatDoesNotExist"},
43
+ "weight": 100,
44
+ },
45
+ )
46
+
47
+ # --- Stage Definition ---
48
+ ApprovalWorkflowStageDefinition.objects.update_or_create(
49
+ name="Approval by nautobot-default-scheduledjob-approver",
50
+ defaults={
51
+ "approval_workflow_definition": awf_def,
52
+ "sequence": 10,
53
+ "min_approvers": 1,
54
+ "denial_message": "This Job requires an approval from nautobot-default-scheduledjob-approver.",
55
+ "approver_group": groups["nautobot-default-scheduledjob-approver"],
56
+ },
57
+ )
58
+
59
+ # --- Object Permissions ---
60
+ perms_data = [
61
+ {
62
+ "name": "nautobot-default-scheduledjobs-architect-permissions",
63
+ "description": "Nautobot added permission aligned to the Workflow Architect persona.",
64
+ "enabled": True,
65
+ "actions": ["view", "add", "change", "delete"],
66
+ "object_types": [ct_workflow_def.id, ct_stage_def.id],
67
+ "groups": [groups["nautobot-default-scheduledjob-architect"]],
68
+ },
69
+ {
70
+ "name": "nautobot-default-scheduledjobs-approver-permissions",
71
+ "description": "Nautobot added permission aligned to the Workflow Approver persona.",
72
+ "enabled": True,
73
+ "actions": ["view", "change"],
74
+ "object_types": [ct_stage.id],
75
+ "groups": [groups["nautobot-default-scheduledjob-approver"]],
76
+ },
77
+ {
78
+ "name": "nautobot-default-scheduledjobs-operator-permissions",
79
+ "description": "Nautobot added permission aligned to the Workflow Operator persona.",
80
+ "enabled": True,
81
+ "actions": ["view"],
82
+ "object_types": [ct_workflow.id, ct_stage.id, ct_response.id],
83
+ "groups": [
84
+ groups["nautobot-default-scheduledjob-operator"],
85
+ groups["nautobot-default-scheduledjob-approver"],
86
+ groups["nautobot-default-scheduledjob-architect"],
87
+ ],
88
+ },
89
+ ]
90
+
91
+ for perm in perms_data:
92
+ obj, _ = ObjectPermission.objects.update_or_create(
93
+ name=perm["name"],
94
+ defaults={
95
+ "description": perm["description"],
96
+ "enabled": perm["enabled"],
97
+ "actions": perm["actions"],
98
+ },
99
+ )
100
+ obj.object_types.set(perm["object_types"])
101
+ obj.groups.set(perm["groups"])
102
+ obj.save()
103
+
104
+
105
+ def reverse_default_scheduledjob_workflow(apps, schema_editor):
106
+ ObjectPermission = apps.get_model("users", "ObjectPermission")
107
+ ApprovalWorkflowDefinition = apps.get_model("extras", "ApprovalWorkflowDefinition")
108
+
109
+ ObjectPermission.objects.filter(
110
+ name__in=[
111
+ "nautobot-default-scheduledjobs-architect-permissions",
112
+ "nautobot-default-scheduledjobs-approver-permissions",
113
+ "nautobot-default-scheduledjobs-operator-permissions",
114
+ ]
115
+ ).delete()
116
+
117
+ ApprovalWorkflowDefinition.objects.filter(name="Scheduled Jobs Approval - Example").delete()
118
+
119
+
120
+ class Migration(migrations.Migration):
121
+ dependencies = [
122
+ ("extras", "0131_configcontext_device_families"),
123
+ ]
124
+
125
+ operations = [
126
+ migrations.RunPython(create_default_scheduledjob_workflow, reverse_default_scheduledjob_workflow),
127
+ ]
@@ -377,6 +377,16 @@ class ApprovalWorkflowStage(OrganizationalModel):
377
377
  # Check if the stage is the active stage and if the stage state is pending
378
378
  return self.pk == active_stage.pk and active_stage.state == ApprovalWorkflowStateChoices.PENDING
379
379
 
380
+ @property
381
+ def is_not_done_stage(self):
382
+ """
383
+ Check if the stage is not done (approved or denied).
384
+
385
+ Returns:
386
+ bool: True if the stage is not APPROVED or DENIED
387
+ """
388
+ return self.state == ApprovalWorkflowStateChoices.PENDING
389
+
380
390
  @property
381
391
  def users_that_already_approved(self):
382
392
  """
@@ -518,7 +528,7 @@ class ApprovalWorkflowStageResponse(BaseModel):
518
528
  max_length=CHARFIELD_MAX_LENGTH,
519
529
  choices=ApprovalWorkflowStateChoices,
520
530
  default=ApprovalWorkflowStateChoices.PENDING,
521
- help_text="User response to this approval workflow stage instance. Eligible values are: Pending, Approved, Denied.",
531
+ help_text="User response to this approval workflow stage instance. Eligible values are: Pending, Comment, Approved, Denied.",
522
532
  )
523
533
  documentation_static_path = "docs/user-guide/platform-functionality/approval-workflow.html"
524
534
  is_version_controlled = False
@@ -25,6 +25,7 @@ from nautobot.core.models import BaseManager, BaseModel
25
25
  from nautobot.core.models.fields import ForeignKeyWithAutoRelatedName, LaxURLField
26
26
  from nautobot.core.models.generics import OrganizationalModel, PrimaryModel
27
27
  from nautobot.core.utils.data import deepmerge, render_jinja2
28
+ from nautobot.core.utils.lookup import get_filterset_for_model, get_model_for_view_name
28
29
  from nautobot.extras.choices import (
29
30
  ButtonClassChoices,
30
31
  WebhookHttpMethodChoices,
@@ -918,6 +919,24 @@ class SavedView(BaseModel, ChangeLoggedModel):
918
919
 
919
920
  super().save(*args, **kwargs)
920
921
 
922
+ @property
923
+ def model(self):
924
+ """
925
+ Return the model class associated with this SavedView, based on the 'view' field.
926
+ """
927
+ return get_model_for_view_name(self.view)
928
+
929
+ def get_filtered_queryset(self, user):
930
+ """
931
+ Return a queryset for the associated model, filtered by this SavedView's filter_params.
932
+ """
933
+ model = self.model
934
+ if model is None:
935
+ return None
936
+ filter_params = self.config.get("filter_params", {})
937
+ filterset_class = get_filterset_for_model(model)
938
+ return filterset_class(filter_params, model.objects.restrict(user)).qs
939
+
921
940
 
922
941
  @extras_features("graphql")
923
942
  class UserSavedViewAssociation(BaseModel):
@@ -285,7 +285,9 @@ class RelationshipModel(models.Model):
285
285
  Q(**side_query_params) | Q(**peer_side_query_params)
286
286
  ).distinct()
287
287
  if not relationship.has_many(peer_side):
288
- resp[side][relationship] = resp[side][relationship].first()
288
+ resp[RelationshipSideChoices.SIDE_PEER][relationship] = resp[
289
+ RelationshipSideChoices.SIDE_PEER
290
+ ][relationship].first()
289
291
  else:
290
292
  # Maybe an uninstalled App?
291
293
  # We can't provide a relevant queryset, but we can provide a descriptive string
nautobot/extras/tables.py CHANGED
@@ -147,8 +147,8 @@ IMAGEATTACHMENT_NAME = """
147
147
  IMAGEATTACHMENT_SIZE = """{{ value|filesizeformat }}"""
148
148
 
149
149
  JOB_BUTTONS = """
150
- <li><a href="{% url 'extras:job' pk=record.pk %}" class="dropdown-item"><span class="mdi mdi-information-outline" aria-hidden="true"></span>Details</a>
151
- <li><a href="{% url 'extras:jobresult_list' %}?job_model={{ record.name | urlencode }}" class="dropdown-item"><span class="mdi mdi-format-list-bulleted" aria-hidden="true"></span>Job Results</a>
150
+ <li><a href="{% url 'extras:job' pk=record.pk %}" class="dropdown-item"><span class="mdi mdi-information-outline" aria-hidden="true"></span>Details</a></li>
151
+ <li><a href="{% url 'extras:jobresult_list' %}?job_model={{ record.name | urlencode }}" class="dropdown-item"><span class="mdi mdi-format-list-bulleted" aria-hidden="true"></span>Job Results</a></li>
152
152
  """
153
153
 
154
154
  JOB_RESULT_BUTTONS = """
@@ -336,19 +336,19 @@ class ApprovalWorkflowTable(BaseTable):
336
336
  model = ApprovalWorkflow
337
337
  fields = (
338
338
  "pk",
339
- "approval_workflow_definition",
340
339
  "object_under_review_content_type",
341
340
  "object_under_review",
342
- "current_state",
343
341
  "user",
342
+ "current_state",
343
+ "approval_workflow_definition",
344
344
  )
345
345
  default_columns = (
346
346
  "pk",
347
- "approval_workflow_definition",
348
347
  "object_under_review_content_type",
349
348
  "object_under_review",
350
- "current_state",
351
349
  "user",
350
+ "current_state",
351
+ "approval_workflow_definition",
352
352
  "actions",
353
353
  )
354
354
 
@@ -385,7 +385,9 @@ class ApprovalWorkflowStageTable(BaseTable):
385
385
  verbose_name="Actions Needed",
386
386
  )
387
387
  state = ApprovalChoiceFieldColumn()
388
- actions = ApprovalButtonsColumn(ApprovalWorkflowStage, buttons=("detail", "changelog", "approve", "deny"))
388
+ actions = ApprovalButtonsColumn(
389
+ ApprovalWorkflowStage, buttons=("detail", "changelog", "comment", "approve", "deny")
390
+ )
389
391
 
390
392
  class Meta(BaseTable.Meta):
391
393
  """Meta attributes."""
@@ -424,10 +426,11 @@ class ApproverDashboardTable(ApprovalWorkflowStageTable):
424
426
  template_code="<a href={{record.approval_workflow.get_absolute_url}}>{{ record.approval_workflow_stage_definition.name }}</a>",
425
427
  verbose_name="Current Stage",
426
428
  )
429
+ approval_workflow__object_under_review_content_type = tables.Column(verbose_name="Object Type Under Review")
427
430
  object_under_review = tables.TemplateColumn(
428
431
  template_code="<a href={{record.approval_workflow.object_under_review.get_absolute_url }}>{{ record.approval_workflow.object_under_review }}</a>"
429
432
  )
430
- actions = ApprovalButtonsColumn(ApprovalWorkflowStage, buttons=("approve", "deny"))
433
+ actions = ApprovalButtonsColumn(ApprovalWorkflowStage, buttons=("approve", "comment", "deny"))
431
434
 
432
435
  class Meta(BaseTable.Meta):
433
436
  """Meta attributes."""
@@ -435,6 +438,7 @@ class ApproverDashboardTable(ApprovalWorkflowStageTable):
435
438
  model = ApprovalWorkflowStage
436
439
  fields = (
437
440
  "pk",
441
+ "approval_workflow__object_under_review_content_type",
438
442
  "object_under_review",
439
443
  "approval_workflow",
440
444
  "approval_workflow_stage",
@@ -444,9 +448,10 @@ class ApproverDashboardTable(ApprovalWorkflowStageTable):
444
448
  )
445
449
  default_columns = (
446
450
  "pk",
447
- "approval_workflow_stage",
448
- "approval_workflow",
451
+ "approval_workflow__object_under_review_content_type",
449
452
  "object_under_review",
453
+ "approval_workflow",
454
+ "approval_workflow_stage",
450
455
  "actions_needed",
451
456
  "state",
452
457
  "actions",
@@ -462,7 +467,7 @@ class RelatedApprovalWorkflowStageTable(ApprovalWorkflowStageTable):
462
467
  template_code="<a href={{record.get_absolute_url}}>{{ record.approval_workflow_stage_definition.name }}</a>",
463
468
  verbose_name="Stage",
464
469
  )
465
- actions = ApprovalButtonsColumn(ApprovalWorkflowStage, buttons=("approve", "deny"))
470
+ actions = ApprovalButtonsColumn(ApprovalWorkflowStage, buttons=("approve", "comment", "deny"))
466
471
 
467
472
  class Meta(BaseTable.Meta):
468
473
  """Meta attributes."""
@@ -512,6 +517,9 @@ class ApprovalWorkflowStageResponseTable(BaseTable):
512
517
  "state",
513
518
  )
514
519
 
520
+ def render_comments(self, value):
521
+ return render_markdown(value)
522
+
515
523
 
516
524
  class RelatedApprovalWorkflowStageResponseTable(ApprovalWorkflowStageResponseTable):
517
525
  """Table for ApprovalWorkflowStageResponse list view."""
@@ -766,8 +774,8 @@ class DynamicGroupTable(BaseTable):
766
774
  class DynamicGroupMembershipTable(DynamicGroupTable):
767
775
  """Hybrid table for displaying info for both group and membership."""
768
776
 
769
- description = tables.Column(accessor="group.description")
770
- members = tables.Column(accessor="group.count", verbose_name="Group Members", orderable=False)
777
+ description = tables.Column(accessor="group__description")
778
+ members = tables.Column(accessor="group__count", verbose_name="Group Members", orderable=False)
771
779
 
772
780
  class Meta(BaseTable.Meta):
773
781
  model = DynamicGroupMembership
@@ -1118,7 +1126,7 @@ class JobTable(BaseTable):
1118
1126
 
1119
1127
  def render_name(self, value):
1120
1128
  return format_html(
1121
- '<span class="btn btn-primary btn-xs"><i class="mdi mdi-play"></i></span>{}',
1129
+ '<span class="btn btn-primary btn-sm p-2 rounded-circle"><span class="mdi mdi-play"></span></span>{}',
1122
1130
  value,
1123
1131
  )
1124
1132
 
@@ -1464,7 +1472,7 @@ class NoteTable(BaseTable):
1464
1472
 
1465
1473
  class Meta(BaseTable.Meta):
1466
1474
  model = Note
1467
- fields = ("created", "last_updated", "note", "user_name")
1475
+ fields = ("created", "last_updated", "note", "user_name", "actions")
1468
1476
 
1469
1477
  def render_note(self, value):
1470
1478
  return render_markdown(value)
@@ -1653,7 +1661,7 @@ class RoleTable(BaseTable):
1653
1661
 
1654
1662
  class Meta(BaseTable.Meta):
1655
1663
  model = Role
1656
- fields = ["pk", "name", "color", "weight", "content_types", "description"]
1664
+ fields = ["pk", "name", "color", "weight", "content_types", "description", "actions"]
1657
1665
 
1658
1666
 
1659
1667
  class RoleTableMixin(BaseTable):
@@ -1673,6 +1681,7 @@ class SecretTable(BaseTable):
1673
1681
  pk = ToggleColumn()
1674
1682
  name = tables.LinkColumn()
1675
1683
  tags = TagColumn(url_name="extras:secret_list")
1684
+ actions = ButtonsColumn(Secret)
1676
1685
 
1677
1686
  class Meta(BaseTable.Meta):
1678
1687
  model = Secret
@@ -1682,6 +1691,7 @@ class SecretTable(BaseTable):
1682
1691
  "provider",
1683
1692
  "description",
1684
1693
  "tags",
1694
+ "actions",
1685
1695
  )
1686
1696
  default_columns = (
1687
1697
  "pk",
@@ -1689,6 +1699,7 @@ class SecretTable(BaseTable):
1689
1699
  "provider",
1690
1700
  "description",
1691
1701
  "tags",
1702
+ "actions",
1692
1703
  )
1693
1704
 
1694
1705
  def render_provider(self, value):
@@ -1700,6 +1711,7 @@ class SecretsGroupTable(BaseTable):
1700
1711
 
1701
1712
  pk = ToggleColumn()
1702
1713
  name = tables.LinkColumn()
1714
+ actions = ButtonsColumn(SecretsGroup)
1703
1715
 
1704
1716
  class Meta(BaseTable.Meta):
1705
1717
  model = SecretsGroup
@@ -1707,11 +1719,13 @@ class SecretsGroupTable(BaseTable):
1707
1719
  "pk",
1708
1720
  "name",
1709
1721
  "description",
1722
+ "actions",
1710
1723
  )
1711
1724
  default_columns = (
1712
1725
  "pk",
1713
1726
  "name",
1714
1727
  "description",
1728
+ "actions",
1715
1729
  )
1716
1730
 
1717
1731
 
@@ -1818,6 +1832,7 @@ class WebhookTable(BaseTable):
1818
1832
  type_update = BooleanColumn()
1819
1833
  type_delete = BooleanColumn()
1820
1834
  ssl_verification = BooleanColumn()
1835
+ actions = ButtonsColumn(Webhook)
1821
1836
 
1822
1837
  class Meta(BaseTable.Meta):
1823
1838
  model = Webhook
@@ -1834,6 +1849,7 @@ class WebhookTable(BaseTable):
1834
1849
  "type_delete",
1835
1850
  "ssl_verification",
1836
1851
  "ca_file_path",
1852
+ "actions",
1837
1853
  )
1838
1854
  default_columns = (
1839
1855
  "pk",
@@ -1842,6 +1858,7 @@ class WebhookTable(BaseTable):
1842
1858
  "payload_url",
1843
1859
  "http_content_type",
1844
1860
  "enabled",
1861
+ "actions",
1845
1862
  )
1846
1863
 
1847
1864
 
@@ -1853,8 +1870,8 @@ class AssociatedContactsTable(StatusTableMixin, RoleTableMixin, BaseTable):
1853
1870
  attrs={"td": {"style": "width:20px;"}},
1854
1871
  )
1855
1872
  name = tables.TemplateColumn(CONTACT_OR_TEAM, verbose_name="Name")
1856
- contact_or_team_phone = tables.TemplateColumn(PHONE, accessor="contact_or_team.phone", verbose_name="Phone")
1857
- contact_or_team_email = tables.TemplateColumn(EMAIL, accessor="contact_or_team.email", verbose_name="E-Mail")
1873
+ contact_or_team_phone = tables.TemplateColumn(PHONE, accessor="contact_or_team__phone", verbose_name="Phone")
1874
+ contact_or_team_email = tables.TemplateColumn(EMAIL, accessor="contact_or_team__email", verbose_name="E-Mail")
1858
1875
  actions = actions = ButtonsColumn(model=ContactAssociation, buttons=("edit", "delete"))
1859
1876
 
1860
1877
  class Meta(BaseTable.Meta):
@@ -1,11 +1,18 @@
1
1
  {% extends 'utilities/confirmation_form.html' %}
2
2
  {% load form_helpers %}
3
+ {% load helpers %}
3
4
 
4
- {% block title %}Approve {{ obj_type }}?{% endblock %}
5
+
6
+ {% block title %}Approve Workflow Stage?{% endblock %}
5
7
 
6
8
  {% block message %}
7
9
  {% block message_extra %}{% endblock %}
8
- <p>Are you sure you want to approve <strong>{{ obj }}</strong> for <strong> {{ object_under_review }}</strong>?</p>
10
+ <p>Are you sure you want to approve</p>
11
+ <ul>
12
+ <li>Stage <strong>{{ obj.approval_workflow_stage_definition.name }}</strong></li>
13
+ <li>from Workflow <strong>{{ obj.approval_workflow_stage_definition.approval_workflow_definition.name }}</strong></li>
14
+ <li>for {{ object_under_review|meta:"verbose_name"|bettertitle }} <strong>{{ object_under_review|hyperlinked_object}}</strong>?</li>
15
+ </ul>
9
16
  <p>You can leave optional comments here.</p>
10
17
  {% render_field form.comments %}
11
18
  {% endblock %}
@@ -1,10 +1,16 @@
1
1
  {% extends 'utilities/confirmation_form.html' %}
2
2
  {% load form_helpers %}
3
+ {% load helpers %}
3
4
 
4
- {% block title %}Deny {{ obj_type }}?{% endblock %}
5
+ {% block title %}Deny Workflow Stage?{% endblock %}
5
6
 
6
7
  {% block message %}
7
- <p>Are you sure you want to deny <strong>{{ obj }}</strong> for <strong> {{ object_under_review }}</strong>?</p>
8
+ <p>Are you sure you want to deny</p>
9
+ <ul>
10
+ <li>Stage <strong>{{ obj.approval_workflow_stage_definition.name }}</strong></li>
11
+ <li>from Workflow <strong>{{ obj.approval_workflow_stage_definition.approval_workflow_definition.name }}</strong></li>
12
+ <li>for {{ object_under_review|meta:"verbose_name"|bettertitle }} <strong>{{ object_under_review|hyperlinked_object}}</strong>?</li>
13
+ </ul>
8
14
  <p>You can leave optional comments here.</p>
9
15
  {% render_field form.comments %}
10
- {% endblock %}
16
+ {% endblock %}
@@ -14,7 +14,7 @@
14
14
  </div>
15
15
  <div class="card">
16
16
  <div class="card-header"><strong>Approval Workflow Stage Definitions</strong></div>
17
- <div class="card-body">
17
+ <div class="card-body overflow-auto">
18
18
  <table class="table" id="approval-workflow-stages">
19
19
  {{ stages.management_form }}
20
20
  {% for stage in stages.forms %}
@@ -34,7 +34,7 @@
34
34
  </div>
35
35
  <div class="card">
36
36
  <div class="card-header"><strong>Custom Field Choices</strong></div>
37
- <div class="card-body">
37
+ <div class="card-body overflow-auto">
38
38
  {% if choices.errors %}
39
39
  <div class="text-danger">
40
40
  Please correct the error(s) below:
@@ -81,7 +81,7 @@
81
81
  </span>
82
82
  {% endif %}
83
83
  </div>
84
- <div class="tab-pane" id="children-form">
84
+ <div class="tab-pane overflow-auto" id="children-form">
85
85
  {% if children.errors %}
86
86
  <div class="text-danger">
87
87
  Please correct the error(s) below:
@@ -192,7 +192,7 @@
192
192
  });
193
193
 
194
194
  // Instead of re-weight on every input change, just do it before we submit the form.
195
- $('form.form').submit((e) => {
195
+ $('#nb-create-form').submit((e) => {
196
196
  $(e.target).find('#children-form').each((i, cf) => { update_weights(cf) });
197
197
  });
198
198
  </script>
@@ -8,7 +8,7 @@
8
8
  <span class="mdi mdi-history"></span>
9
9
  </a>
10
10
  {% endif %}
11
- {% if "approve" in buttons and have_permission and record.is_active_stage %}
11
+ {% if "approve" in buttons and have_permission and record.is_active_stage and can_approve %}
12
12
  <span title='{% if request.user in record.users_that_already_approved %}You already approved this stage{% else %}Approve this stage{% endif %}'>
13
13
  {% if request.user not in record.users_that_already_approved %}
14
14
  <a href="{% url approval_route pk=record.pk %}?return_url={{ request.path }}{{ return_url_extra }}"
@@ -22,7 +22,7 @@
22
22
  {% endif %}
23
23
  </span>
24
24
  {% endif %}
25
- {% if "deny" in buttons and have_permission and record.is_active_stage and request.user not in record.users_that_already_approved %}
25
+ {% if "deny" in buttons and have_permission and record.is_active_stage and request.user not in record.users_that_already_approved and can_approve %}
26
26
  <span title='{%if request.user in record.users_that_already_approved %}You already approved this stage{% else %}Deny this stage{% endif %}'>
27
27
  {% if request.user not in record.users_that_already_approved %}
28
28
  <a href="{% url deny_route pk=record.pk %}?return_url={{ request.path }}{{ return_url_extra }}"
@@ -36,3 +36,11 @@
36
36
  {% endif %}
37
37
  </span>
38
38
  {% endif %}
39
+ {% if "comment" in buttons and have_permission and record.is_not_done_stage %}
40
+ <span title='Comment this stage'>
41
+ <a href="{% url comment_route pk=record.pk %}?return_url={{ request.path }}{{ return_url_extra }}"
42
+ class="btn btn-secondary btn-sm">
43
+ <span class="mdi mdi-comment-text-outline"></span>
44
+ </a>
45
+ </span>
46
+ {% endif %}
@@ -21,14 +21,14 @@
21
21
 
22
22
  {% if perms.extras.run_job and row.record.runnable %}
23
23
  <a href="{% url 'extras:job_run' pk=row.record.pk %}"
24
- class="btn btn-primary btn-sm"
24
+ class="btn btn-primary btn-sm p-2 rounded-circle"
25
25
  data-bs-toggle="tooltip"
26
26
  data-bs-title="Run/Schedule">
27
27
  <span class="mdi mdi-play" aria-hidden="true"><span class="visually-hidden">Run/Schedule</span></span>
28
28
  </a>
29
29
  {% else %}
30
30
  <a aria-disabled="true"
31
- class="btn btn-primary btn-sm disabled"
31
+ class="btn btn-primary btn-sm disabled p-2 rounded-circle"
32
32
  data-bs-toggle="tooltip"
33
33
  data-bs-title="Run/Schedule">
34
34
  <span class="mdi mdi-play" aria-hidden="true"><span class="visually-hidden">Run/Schedule</span></span>
@@ -88,7 +88,7 @@
88
88
  <div class="card-header">
89
89
  <strong>Logs</strong>
90
90
  <div class="float-end d-print-none">
91
- <input class="form-control" id="log-filter" type="text" placeholder="Filter log level or message" title="Filter log level or message" style="height: 23px" />
91
+ <input class="form-control" id="log-filter" type="text" placeholder="Filter log level or message" title="Filter log level or message" style="height: 23px; width: 190px;" />
92
92
  </div>
93
93
  </div>
94
94
  {% if result and result.pk %}
@@ -19,7 +19,7 @@
19
19
  </div>
20
20
  <div class="card">
21
21
  <div class="card-header"><strong>Choices (select and multi-select data types)</strong></div>
22
- <div class="card-body">
22
+ <div class="card-body overflow-auto">
23
23
  {{ choices.non_field_errors }}
24
24
  <table class="table" id="metadata-choices">
25
25
  {{ choices.management_form }}
@@ -2,9 +2,6 @@
2
2
  {% load approvals %}
3
3
  {% load helpers %}
4
4
 
5
-
6
- {% block title %}{{ block.super }} - Approval Workflow{% endblock %}
7
-
8
5
  {% block content %}
9
6
  <div class="row">
10
7
  <div class="col-lg-6">
@@ -30,6 +27,8 @@
30
27
  </div>
31
28
  <div class="col-lg-6">
32
29
  {% include 'panel_table.html' with table=stage_table heading="Stages"%}
30
+ </div>
31
+ <div class="full-width">
33
32
  {% include 'panel_table.html' with table=response_table heading="Responses"%}
34
33
  </div>
35
34
  </div>
@@ -12,7 +12,7 @@
12
12
  </div>
13
13
  <div class="card">
14
14
  <div class="card-header"><strong>Secret Assignment</strong></div>
15
- <div class="card-body">
15
+ <div class="card-body overflow-auto">
16
16
  {% if secrets.errors %}
17
17
  <div class="text-danger">
18
18
  Please correct the error(s) below: