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.
- nautobot/apps/forms.py +8 -0
- nautobot/apps/templatetags.py +231 -0
- nautobot/apps/testing.py +11 -1
- nautobot/apps/ui.py +21 -1
- nautobot/apps/utils.py +26 -1
- nautobot/core/celery/__init__.py +46 -1
- nautobot/core/cli/bootstrap_v3_to_v5.py +185 -44
- nautobot/core/cli/bootstrap_v3_to_v5_changes.yaml +314 -0
- nautobot/core/graphql/generators.py +2 -2
- nautobot/core/jobs/bulk_actions.py +12 -6
- nautobot/core/jobs/cleanup.py +13 -1
- nautobot/core/settings.py +13 -0
- nautobot/core/settings.yaml +22 -0
- nautobot/core/settings_funcs.py +11 -1
- nautobot/core/tables.py +19 -1
- nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
- nautobot/core/templates/components/panel/header_extra_content_table.html +9 -3
- nautobot/core/templates/generic/object_create.html +1 -1
- nautobot/core/templates/inc/header.html +9 -10
- nautobot/core/templates/login.html +16 -1
- nautobot/core/templates/nautobot_config.py.j2 +14 -1
- nautobot/core/templates/redoc_ui.html +3 -0
- nautobot/core/templatetags/helpers.py +3 -3
- nautobot/core/testing/views.py +3 -1
- nautobot/core/tests/test_graphql.py +13 -0
- nautobot/core/tests/test_jobs.py +118 -0
- nautobot/core/tests/test_views.py +24 -0
- nautobot/core/ui/bulk_buttons.py +2 -3
- nautobot/core/utils/lookup.py +2 -3
- nautobot/core/utils/permissions.py +1 -1
- nautobot/core/views/generic.py +1 -0
- nautobot/core/views/mixins.py +37 -10
- nautobot/core/views/renderers.py +1 -0
- nautobot/core/views/utils.py +3 -3
- nautobot/data_validation/views.py +1 -9
- nautobot/dcim/forms.py +9 -9
- nautobot/dcim/models/devices.py +3 -3
- nautobot/dcim/tables/power.py +3 -0
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +1 -1
- nautobot/dcim/views.py +30 -44
- nautobot/extras/api/views.py +14 -3
- nautobot/extras/choices.py +3 -0
- nautobot/extras/jobs.py +48 -2
- nautobot/extras/migrations/0132_approval_workflow_seed_data.py +127 -0
- nautobot/extras/models/approvals.py +11 -1
- nautobot/extras/models/models.py +19 -0
- nautobot/extras/models/relationships.py +3 -1
- nautobot/extras/tables.py +35 -18
- nautobot/extras/templates/extras/approval_workflow/approve.html +9 -2
- nautobot/extras/templates/extras/approval_workflow/deny.html +9 -3
- nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +1 -1
- nautobot/extras/templates/extras/customfield_update.html +1 -1
- nautobot/extras/templates/extras/dynamicgroup_update.html +2 -2
- nautobot/extras/templates/extras/inc/approval_buttons_column.html +10 -2
- nautobot/extras/templates/extras/inc/job_tiles.html +2 -2
- nautobot/extras/templates/extras/inc/jobresult.html +1 -1
- nautobot/extras/templates/extras/metadatatype_create.html +1 -1
- nautobot/extras/templates/extras/object_approvalworkflow.html +2 -3
- nautobot/extras/templates/extras/secretsgroup_update.html +1 -1
- nautobot/extras/tests/test_api.py +57 -3
- nautobot/extras/tests/test_customfields_filters.py +84 -4
- nautobot/extras/tests/test_views.py +323 -6
- nautobot/extras/views.py +114 -39
- nautobot/ipam/constants.py +2 -2
- nautobot/ipam/tables.py +7 -6
- nautobot/load_balancers/constants.py +6 -0
- nautobot/load_balancers/migrations/0001_initial.py +14 -3
- nautobot/load_balancers/models.py +5 -4
- nautobot/load_balancers/tables.py +5 -0
- nautobot/project-static/dist/css/nautobot.css +1 -1
- nautobot/project-static/dist/css/nautobot.css.map +1 -1
- nautobot/project-static/dist/js/graphql-libraries.js +1 -1
- nautobot/project-static/dist/js/graphql-libraries.js.map +1 -1
- nautobot/project-static/dist/js/libraries.js +1 -1
- nautobot/project-static/dist/js/libraries.js.LICENSE.txt +38 -2
- nautobot/project-static/dist/js/libraries.js.map +1 -1
- nautobot/project-static/dist/js/nautobot-graphiql.js +1 -1
- nautobot/project-static/dist/js/nautobot-graphiql.js.map +1 -1
- nautobot/project-static/dist/js/nautobot.js +1 -1
- nautobot/project-static/dist/js/nautobot.js.map +1 -1
- nautobot/project-static/img/dark-theme.png +0 -0
- nautobot/project-static/img/light-theme.png +0 -0
- nautobot/project-static/img/system-theme.png +0 -0
- nautobot/project-static/js/forms.js +1 -85
- nautobot/tenancy/tables.py +3 -2
- nautobot/tenancy/views.py +3 -2
- nautobot/ui/package-lock.json +553 -569
- nautobot/ui/package.json +10 -10
- nautobot/ui/src/js/checkbox.js +132 -0
- nautobot/ui/src/js/nautobot.js +6 -0
- nautobot/ui/src/js/select2.js +69 -73
- nautobot/ui/src/js/theme.js +129 -39
- nautobot/ui/src/scss/nautobot.scss +11 -1
- nautobot/vpn/templates/vpn/vpnprofile_create.html +2 -2
- nautobot/wireless/filters.py +15 -1
- nautobot/wireless/tables.py +18 -14
- nautobot/wireless/templates/wireless/wirelessnetwork_create.html +1 -1
- {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/METADATA +2 -2
- {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/RECORD +103 -98
- {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/LICENSE.txt +0 -0
- {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/NOTICE +0 -0
- {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/WHEEL +0 -0
- {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
|
nautobot/extras/models/models.py
CHANGED
|
@@ -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[
|
|
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(
|
|
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
|
-
"
|
|
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="
|
|
770
|
-
members = tables.Column(accessor="
|
|
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-
|
|
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="
|
|
1857
|
-
contact_or_team_email = tables.TemplateColumn(EMAIL, accessor="
|
|
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
|
-
|
|
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
|
|
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
|
|
5
|
+
{% block title %}Deny Workflow Stage?{% endblock %}
|
|
5
6
|
|
|
6
7
|
{% block message %}
|
|
7
|
-
<p>Are you sure you want to deny
|
|
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
|
|
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:
|