mirror of
https://github.com/ansible/awx.git
synced 2026-03-09 05:29:26 -02:30
Merge pull request #4264 from beeankha/workflow_pause_approve
Workflow Approval Nodes
Reviewed-by: Ryan Petrello
https://github.com/ryanpetrello
This commit is contained in:
@@ -34,7 +34,8 @@ from rest_framework.negotiation import DefaultContentNegotiation
|
||||
# AWX
|
||||
from awx.api.filters import FieldLookupBackend
|
||||
from awx.main.models import (
|
||||
UnifiedJob, UnifiedJobTemplate, User, Role, Credential
|
||||
UnifiedJob, UnifiedJobTemplate, User, Role, Credential,
|
||||
WorkflowJobTemplateNode, WorkflowApprovalTemplate
|
||||
)
|
||||
from awx.main.access import access_registry
|
||||
from awx.main.utils import (
|
||||
@@ -882,6 +883,21 @@ class CopyAPIView(GenericAPIView):
|
||||
create_kwargs[field.name] = CopyAPIView._decrypt_model_field_if_needed(
|
||||
obj, field.name, field_val
|
||||
)
|
||||
|
||||
# WorkflowJobTemplateNodes that represent an approval are *special*;
|
||||
# when we copy them, we actually want to *copy* the UJT they point at
|
||||
# rather than share the template reference between nodes in disparate
|
||||
# workflows
|
||||
if (
|
||||
isinstance(obj, WorkflowJobTemplateNode) and
|
||||
isinstance(getattr(obj, 'unified_job_template'), WorkflowApprovalTemplate)
|
||||
):
|
||||
new_approval_template, sub_objs = CopyAPIView.copy_model_obj(
|
||||
None, None, WorkflowApprovalTemplate,
|
||||
obj.unified_job_template, creater
|
||||
)
|
||||
create_kwargs['unified_job_template'] = new_approval_template
|
||||
|
||||
new_obj = model.objects.create(**create_kwargs)
|
||||
logger.debug('Deep copy: Created new object {}({})'.format(
|
||||
new_obj, model
|
||||
|
||||
@@ -17,7 +17,7 @@ logger = logging.getLogger('awx.api.permissions')
|
||||
|
||||
__all__ = ['ModelAccessPermission', 'JobTemplateCallbackPermission', 'VariableDataPermission',
|
||||
'TaskPermission', 'ProjectUpdatePermission', 'InventoryInventorySourcesUpdatePermission',
|
||||
'UserPermission', 'IsSuperUser', 'InstanceGroupTowerPermission',]
|
||||
'UserPermission', 'IsSuperUser', 'InstanceGroupTowerPermission', 'WorkflowApprovalPermission']
|
||||
|
||||
|
||||
class ModelAccessPermission(permissions.BasePermission):
|
||||
@@ -196,6 +196,17 @@ class TaskPermission(ModelAccessPermission):
|
||||
return False
|
||||
|
||||
|
||||
class WorkflowApprovalPermission(ModelAccessPermission):
|
||||
'''
|
||||
Permission check used by workflow `approval` and `deny` views to determine
|
||||
who has access to approve and deny paused workflow nodes
|
||||
'''
|
||||
|
||||
def check_post_permissions(self, request, view, obj=None):
|
||||
approval = get_object_or_400(view.model, pk=view.kwargs['pk'])
|
||||
return check_user_access(request.user, view.model, 'approve_or_deny', approval)
|
||||
|
||||
|
||||
class ProjectUpdatePermission(ModelAccessPermission):
|
||||
'''
|
||||
Permission check used by ProjectUpdateView to determine who can update projects
|
||||
@@ -238,4 +249,3 @@ class InstanceGroupTowerPermission(ModelAccessPermission):
|
||||
if request.method == 'DELETE' and obj.name == "tower":
|
||||
return False
|
||||
return super(InstanceGroupTowerPermission, self).has_object_permission(request, view, obj)
|
||||
|
||||
|
||||
@@ -50,16 +50,16 @@ from awx.main.constants import (
|
||||
CENSOR_VALUE,
|
||||
)
|
||||
from awx.main.models import (
|
||||
ActivityStream, AdHocCommand, AdHocCommandEvent, Credential,
|
||||
CredentialInputSource, CredentialType, CustomInventoryScript,
|
||||
Group, Host, Instance, InstanceGroup, Inventory, InventorySource,
|
||||
InventoryUpdate, InventoryUpdateEvent, Job, JobEvent, JobHostSummary,
|
||||
JobLaunchConfig, JobNotificationMixin, JobTemplate, Label, Notification,
|
||||
NotificationTemplate, OAuth2AccessToken, OAuth2Application, Organization,
|
||||
Project, ProjectUpdate, ProjectUpdateEvent, RefreshToken, Role, Schedule,
|
||||
StdoutMaxBytesExceeded, SystemJob, SystemJobEvent, SystemJobTemplate,
|
||||
Team, UnifiedJob, UnifiedJobTemplate, WorkflowJob, WorkflowJobNode,
|
||||
WorkflowJobTemplate, WorkflowJobTemplateNode
|
||||
ActivityStream, AdHocCommand, AdHocCommandEvent, Credential, CredentialInputSource,
|
||||
CredentialType, CustomInventoryScript, Group, Host, Instance,
|
||||
InstanceGroup, Inventory, InventorySource, InventoryUpdate,
|
||||
InventoryUpdateEvent, Job, JobEvent, JobHostSummary, JobLaunchConfig,
|
||||
JobNotificationMixin, JobTemplate, Label, Notification, NotificationTemplate,
|
||||
OAuth2AccessToken, OAuth2Application, Organization, Project,
|
||||
ProjectUpdate, ProjectUpdateEvent, RefreshToken, Role, Schedule,
|
||||
SystemJob, SystemJobEvent, SystemJobTemplate, Team, UnifiedJob,
|
||||
UnifiedJobTemplate, WorkflowApproval, WorkflowApprovalTemplate, WorkflowJob,
|
||||
WorkflowJobNode, WorkflowJobTemplate, WorkflowJobTemplateNode, StdoutMaxBytesExceeded
|
||||
)
|
||||
from awx.main.models.base import VERBOSITY_CHOICES, NEW_JOB_TYPE_CHOICES
|
||||
from awx.main.models.rbac import (
|
||||
@@ -121,6 +121,8 @@ SUMMARIZABLE_FK_FIELDS = {
|
||||
'job_template': DEFAULT_SUMMARY_FIELDS,
|
||||
'workflow_job_template': DEFAULT_SUMMARY_FIELDS,
|
||||
'workflow_job': DEFAULT_SUMMARY_FIELDS,
|
||||
'workflow_approval_template': DEFAULT_SUMMARY_FIELDS + ('timeout',),
|
||||
'workflow_approval': DEFAULT_SUMMARY_FIELDS + ('timeout',),
|
||||
'schedule': DEFAULT_SUMMARY_FIELDS + ('next_run',),
|
||||
'unified_job_template': DEFAULT_SUMMARY_FIELDS + ('unified_job_type',),
|
||||
'last_job': DEFAULT_SUMMARY_FIELDS + ('finished', 'status', 'failed', 'license_error'),
|
||||
@@ -681,6 +683,8 @@ class UnifiedJobTemplateSerializer(BaseSerializer):
|
||||
serializer_class = SystemJobTemplateSerializer
|
||||
elif isinstance(obj, WorkflowJobTemplate):
|
||||
serializer_class = WorkflowJobTemplateSerializer
|
||||
elif isinstance(obj, WorkflowApprovalTemplate):
|
||||
serializer_class = WorkflowApprovalTemplateSerializer
|
||||
return serializer_class
|
||||
|
||||
def to_representation(self, obj):
|
||||
@@ -782,6 +786,8 @@ class UnifiedJobSerializer(BaseSerializer):
|
||||
serializer_class = SystemJobSerializer
|
||||
elif isinstance(obj, WorkflowJob):
|
||||
serializer_class = WorkflowJobSerializer
|
||||
elif isinstance(obj, WorkflowApproval):
|
||||
serializer_class = WorkflowApprovalSerializer
|
||||
return serializer_class
|
||||
|
||||
def to_representation(self, obj):
|
||||
@@ -838,6 +844,8 @@ class UnifiedJobListSerializer(UnifiedJobSerializer):
|
||||
serializer_class = SystemJobListSerializer
|
||||
elif isinstance(obj, WorkflowJob):
|
||||
serializer_class = WorkflowJobListSerializer
|
||||
elif isinstance(obj, WorkflowApproval):
|
||||
serializer_class = WorkflowApprovalListSerializer
|
||||
return serializer_class
|
||||
|
||||
def to_representation(self, obj):
|
||||
@@ -3395,6 +3403,76 @@ class WorkflowJobCancelSerializer(WorkflowJobSerializer):
|
||||
fields = ('can_cancel',)
|
||||
|
||||
|
||||
class WorkflowApprovalViewSerializer(UnifiedJobSerializer):
|
||||
|
||||
class Meta:
|
||||
model = WorkflowApproval
|
||||
fields = []
|
||||
|
||||
|
||||
class WorkflowApprovalSerializer(UnifiedJobSerializer):
|
||||
|
||||
can_approve_or_deny = serializers.SerializerMethodField()
|
||||
approval_expiration = serializers.SerializerMethodField()
|
||||
timed_out = serializers.ReadOnlyField()
|
||||
|
||||
class Meta:
|
||||
model = WorkflowApproval
|
||||
fields = ('*', '-controller_node', '-execution_node', 'can_approve_or_deny', 'approval_expiration', 'timed_out',)
|
||||
|
||||
def get_approval_expiration(self, obj):
|
||||
if obj.status != 'pending' or obj.timeout == 0:
|
||||
return None
|
||||
return obj.created + timedelta(seconds=obj.timeout)
|
||||
|
||||
def get_can_approve_or_deny(self, obj):
|
||||
request = self.context.get('request', None)
|
||||
allowed = request.user.can_access(WorkflowApproval, 'approve_or_deny', obj)
|
||||
return allowed is True and obj.status == 'pending'
|
||||
|
||||
def get_related(self, obj):
|
||||
res = super(WorkflowApprovalSerializer, self).get_related(obj)
|
||||
|
||||
if obj.workflow_approval_template:
|
||||
res['workflow_approval_template'] = self.reverse('api:workflow_approval_template_detail',
|
||||
kwargs={'pk': obj.workflow_approval_template.pk})
|
||||
res['approve'] = self.reverse('api:workflow_approval_approve', kwargs={'pk': obj.pk})
|
||||
res['deny'] = self.reverse('api:workflow_approval_deny', kwargs={'pk': obj.pk})
|
||||
return res
|
||||
|
||||
|
||||
class WorkflowApprovalActivityStreamSerializer(WorkflowApprovalSerializer):
|
||||
"""
|
||||
timed_out and status are usually read-only fields
|
||||
However, when we generate an activity stream record, we *want* to record
|
||||
these types of changes. This serializer allows us to do so.
|
||||
"""
|
||||
status = serializers.ChoiceField(choices=JobTemplate.JOB_TEMPLATE_STATUS_CHOICES)
|
||||
timed_out = serializers.BooleanField()
|
||||
|
||||
|
||||
|
||||
class WorkflowApprovalListSerializer(WorkflowApprovalSerializer, UnifiedJobListSerializer):
|
||||
|
||||
class Meta:
|
||||
fields = ('*', '-controller_node', '-execution_node', 'can_approve_or_deny', 'approval_expiration', 'timed_out',)
|
||||
|
||||
|
||||
class WorkflowApprovalTemplateSerializer(UnifiedJobTemplateSerializer):
|
||||
|
||||
class Meta:
|
||||
model = WorkflowApprovalTemplate
|
||||
fields = ('*', 'timeout', 'name',)
|
||||
|
||||
def get_related(self, obj):
|
||||
res = super(WorkflowApprovalTemplateSerializer, self).get_related(obj)
|
||||
if 'last_job' in res:
|
||||
del res['last_job']
|
||||
|
||||
res.update(dict(jobs = self.reverse('api:workflow_approval_template_jobs_list', kwargs={'pk': obj.pk}),))
|
||||
return res
|
||||
|
||||
|
||||
class LaunchConfigurationBaseSerializer(BaseSerializer):
|
||||
scm_branch = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None)
|
||||
job_type = serializers.ChoiceField(allow_blank=True, allow_null=True, required=False, default=None,
|
||||
@@ -3453,6 +3531,10 @@ class LaunchConfigurationBaseSerializer(BaseSerializer):
|
||||
ujt = attrs['unified_job_template']
|
||||
elif self.instance:
|
||||
ujt = self.instance.unified_job_template
|
||||
if ujt is None:
|
||||
if 'workflow_job_template' in attrs:
|
||||
return {'workflow_job_template': attrs['workflow_job_template']}
|
||||
return {}
|
||||
|
||||
# build additional field survey_passwords to track redacted variables
|
||||
password_dict = {}
|
||||
@@ -3534,6 +3616,7 @@ class WorkflowJobTemplateNodeSerializer(LaunchConfigurationBaseSerializer):
|
||||
|
||||
def get_related(self, obj):
|
||||
res = super(WorkflowJobTemplateNodeSerializer, self).get_related(obj)
|
||||
res['create_approval_template'] = self.reverse('api:workflow_job_template_node_create_approval', kwargs={'pk': obj.pk})
|
||||
res['success_nodes'] = self.reverse('api:workflow_job_template_node_success_nodes_list', kwargs={'pk': obj.pk})
|
||||
res['failure_nodes'] = self.reverse('api:workflow_job_template_node_failure_nodes_list', kwargs={'pk': obj.pk})
|
||||
res['always_nodes'] = self.reverse('api:workflow_job_template_node_always_nodes_list', kwargs={'pk': obj.pk})
|
||||
@@ -3553,6 +3636,12 @@ class WorkflowJobTemplateNodeSerializer(LaunchConfigurationBaseSerializer):
|
||||
field_kwargs.pop('queryset', None)
|
||||
return field_class, field_kwargs
|
||||
|
||||
def get_summary_fields(self, obj):
|
||||
summary_fields = super(WorkflowJobTemplateNodeSerializer, self).get_summary_fields(obj)
|
||||
if isinstance(obj.unified_job_template, WorkflowApprovalTemplate):
|
||||
summary_fields['unified_job_template']['timeout'] = obj.unified_job_template.timeout
|
||||
return summary_fields
|
||||
|
||||
|
||||
class WorkflowJobNodeSerializer(LaunchConfigurationBaseSerializer):
|
||||
success_nodes = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
|
||||
@@ -3578,6 +3667,12 @@ class WorkflowJobNodeSerializer(LaunchConfigurationBaseSerializer):
|
||||
res['workflow_job'] = self.reverse('api:workflow_job_detail', kwargs={'pk': obj.workflow_job.pk})
|
||||
return res
|
||||
|
||||
def get_summary_fields(self, obj):
|
||||
summary_fields = super(WorkflowJobNodeSerializer, self).get_summary_fields(obj)
|
||||
if isinstance(obj.job, WorkflowApproval):
|
||||
summary_fields['job']['timed_out'] = obj.job.timed_out
|
||||
return summary_fields
|
||||
|
||||
|
||||
class WorkflowJobNodeListSerializer(WorkflowJobNodeSerializer):
|
||||
pass
|
||||
@@ -3603,6 +3698,16 @@ class WorkflowJobTemplateNodeDetailSerializer(WorkflowJobTemplateNodeSerializer)
|
||||
return field_class, field_kwargs
|
||||
|
||||
|
||||
class WorkflowJobTemplateNodeCreateApprovalSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = WorkflowApprovalTemplate
|
||||
fields = ('timeout', 'name', 'description',)
|
||||
|
||||
def to_representation(self, obj):
|
||||
return {}
|
||||
|
||||
|
||||
class JobListSerializer(JobSerializer, UnifiedJobListSerializer):
|
||||
pass
|
||||
|
||||
@@ -4663,7 +4768,8 @@ class ActivityStreamSerializer(BaseSerializer):
|
||||
('o_auth2_access_token', ('id', 'user_id', 'description', 'application_id', 'scope')),
|
||||
('o_auth2_application', ('id', 'name', 'description')),
|
||||
('credential_type', ('id', 'name', 'description', 'kind', 'managed_by_tower')),
|
||||
('ad_hoc_command', ('id', 'name', 'status', 'limit'))
|
||||
('ad_hoc_command', ('id', 'name', 'status', 'limit')),
|
||||
('workflow_approval', ('id', 'name', 'unified_job_id')),
|
||||
]
|
||||
return field_list
|
||||
|
||||
@@ -4772,6 +4878,8 @@ class ActivityStreamSerializer(BaseSerializer):
|
||||
def _summarize_parent_ujt(self, obj, fk, summary_fields):
|
||||
summary_keys = {'job': 'job_template',
|
||||
'workflow_job_template_node': 'workflow_job_template',
|
||||
'workflow_approval_template': 'workflow_job_template',
|
||||
'workflow_approval': 'workflow_job',
|
||||
'schedule': 'unified_job_template'}
|
||||
if fk not in summary_keys:
|
||||
return
|
||||
|
||||
@@ -71,6 +71,8 @@ from .instance import urls as instance_urls
|
||||
from .instance_group import urls as instance_group_urls
|
||||
from .oauth2 import urls as oauth2_urls
|
||||
from .oauth2_root import urls as oauth2_root_urls
|
||||
from .workflow_approval_template import urls as workflow_approval_template_urls
|
||||
from .workflow_approval import urls as workflow_approval_urls
|
||||
|
||||
|
||||
v2_urls = [
|
||||
@@ -131,8 +133,11 @@ v2_urls = [
|
||||
url(r'^unified_job_templates/$', UnifiedJobTemplateList.as_view(), name='unified_job_template_list'),
|
||||
url(r'^unified_jobs/$', UnifiedJobList.as_view(), name='unified_job_list'),
|
||||
url(r'^activity_stream/', include(activity_stream_urls)),
|
||||
url(r'^workflow_approval_templates/', include(workflow_approval_template_urls)),
|
||||
url(r'^workflow_approvals/', include(workflow_approval_urls)),
|
||||
]
|
||||
|
||||
|
||||
app_name = 'api'
|
||||
urlpatterns = [
|
||||
url(r'^$', ApiRootView.as_view(), name='api_root_view'),
|
||||
|
||||
21
awx/api/urls/workflow_approval.py
Normal file
21
awx/api/urls/workflow_approval.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# Copyright (c) 2017 Ansible, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from awx.api.views import (
|
||||
WorkflowApprovalList,
|
||||
WorkflowApprovalDetail,
|
||||
WorkflowApprovalApprove,
|
||||
WorkflowApprovalDeny,
|
||||
)
|
||||
|
||||
|
||||
urls = [
|
||||
url(r'^$', WorkflowApprovalList.as_view(), name='workflow_approval_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', WorkflowApprovalDetail.as_view(), name='workflow_approval_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/approve/$', WorkflowApprovalApprove.as_view(), name='workflow_approval_approve'),
|
||||
url(r'^(?P<pk>[0-9]+)/deny/$', WorkflowApprovalDeny.as_view(), name='workflow_approval_deny'),
|
||||
]
|
||||
|
||||
__all__ = ['urls']
|
||||
17
awx/api/urls/workflow_approval_template.py
Normal file
17
awx/api/urls/workflow_approval_template.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# Copyright (c) 2017 Ansible, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from awx.api.views import (
|
||||
WorkflowApprovalTemplateDetail,
|
||||
WorkflowApprovalTemplateJobsList,
|
||||
)
|
||||
|
||||
|
||||
urls = [
|
||||
url(r'^(?P<pk>[0-9]+)/$', WorkflowApprovalTemplateDetail.as_view(), name='workflow_approval_template_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/approvals/$', WorkflowApprovalTemplateJobsList.as_view(), name='workflow_approval_template_jobs_list'),
|
||||
]
|
||||
|
||||
__all__ = ['urls']
|
||||
@@ -10,6 +10,7 @@ from awx.api.views import (
|
||||
WorkflowJobTemplateNodeFailureNodesList,
|
||||
WorkflowJobTemplateNodeAlwaysNodesList,
|
||||
WorkflowJobTemplateNodeCredentialsList,
|
||||
WorkflowJobTemplateNodeCreateApproval,
|
||||
)
|
||||
|
||||
|
||||
@@ -20,6 +21,7 @@ urls = [
|
||||
url(r'^(?P<pk>[0-9]+)/failure_nodes/$', WorkflowJobTemplateNodeFailureNodesList.as_view(), name='workflow_job_template_node_failure_nodes_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/always_nodes/$', WorkflowJobTemplateNodeAlwaysNodesList.as_view(), name='workflow_job_template_node_always_nodes_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/credentials/$', WorkflowJobTemplateNodeCredentialsList.as_view(), name='workflow_job_template_node_credentials_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/create_approval_template/$', WorkflowJobTemplateNodeCreateApproval.as_view(), name='workflow_job_template_node_create_approval'),
|
||||
]
|
||||
|
||||
__all__ = ['urls']
|
||||
|
||||
@@ -91,7 +91,8 @@ from awx.main.redact import UriCleaner
|
||||
from awx.api.permissions import (
|
||||
JobTemplateCallbackPermission, TaskPermission, ProjectUpdatePermission,
|
||||
InventoryInventorySourcesUpdatePermission, UserPermission,
|
||||
InstanceGroupTowerPermission, VariableDataPermission
|
||||
InstanceGroupTowerPermission, VariableDataPermission,
|
||||
WorkflowApprovalPermission
|
||||
)
|
||||
from awx.api import renderers
|
||||
from awx.api import serializers
|
||||
@@ -839,8 +840,6 @@ class SystemJobEventsList(SubListAPIView):
|
||||
return super(SystemJobEventsList, self).finalize_response(request, response, *args, **kwargs)
|
||||
|
||||
|
||||
|
||||
|
||||
class ProjectUpdateCancel(RetrieveAPIView):
|
||||
|
||||
model = models.ProjectUpdate
|
||||
@@ -3013,6 +3012,34 @@ class WorkflowJobTemplateNodeChildrenBaseList(EnforceParentRelationshipMixin, Su
|
||||
return None
|
||||
|
||||
|
||||
class WorkflowJobTemplateNodeCreateApproval(RetrieveAPIView):
|
||||
|
||||
model = models.WorkflowJobTemplateNode
|
||||
serializer_class = serializers.WorkflowJobTemplateNodeCreateApprovalSerializer
|
||||
permission_classes = []
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
obj = self.get_object()
|
||||
serializer = self.get_serializer(instance=obj, data=request.data)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
approval_template = obj.create_approval_template(**serializer.validated_data)
|
||||
data = serializers.WorkflowApprovalTemplateSerializer(
|
||||
approval_template,
|
||||
context=self.get_serializer_context()
|
||||
).data
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
def check_permissions(self, request):
|
||||
obj = self.get_object().workflow_job_template
|
||||
if request.method == 'POST':
|
||||
if not request.user.can_access(models.WorkflowJobTemplate, 'change', obj, request.data):
|
||||
self.permission_denied(request)
|
||||
else:
|
||||
if not request.user.can_access(models.WorkflowJobTemplate, 'read', obj):
|
||||
self.permission_denied(request)
|
||||
|
||||
|
||||
class WorkflowJobTemplateNodeSuccessNodesList(WorkflowJobTemplateNodeChildrenBaseList):
|
||||
relationship = 'success_nodes'
|
||||
|
||||
@@ -4405,3 +4432,63 @@ for attr, value in list(locals().items()):
|
||||
name = camelcase_to_underscore(attr)
|
||||
view = value.as_view()
|
||||
setattr(this_module, name, view)
|
||||
|
||||
|
||||
class WorkflowApprovalTemplateDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIView):
|
||||
|
||||
model = models.WorkflowApprovalTemplate
|
||||
serializer_class = serializers.WorkflowApprovalTemplateSerializer
|
||||
|
||||
|
||||
class WorkflowApprovalTemplateJobsList(SubListAPIView):
|
||||
|
||||
model = models.WorkflowApproval
|
||||
serializer_class = serializers.WorkflowApprovalListSerializer
|
||||
parent_model = models.WorkflowApprovalTemplate
|
||||
relationship = 'approvals'
|
||||
parent_key = 'workflow_approval_template'
|
||||
|
||||
|
||||
class WorkflowApprovalList(ListCreateAPIView):
|
||||
|
||||
model = models.WorkflowApproval
|
||||
serializer_class = serializers.WorkflowApprovalListSerializer
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
return super(WorkflowApprovalList, self).get(request, *args, **kwargs)
|
||||
|
||||
|
||||
class WorkflowApprovalDetail(UnifiedJobDeletionMixin, RetrieveDestroyAPIView):
|
||||
|
||||
model = models.WorkflowApproval
|
||||
serializer_class = serializers.WorkflowApprovalSerializer
|
||||
|
||||
|
||||
class WorkflowApprovalApprove(RetrieveAPIView):
|
||||
model = models.WorkflowApproval
|
||||
serializer_class = serializers.WorkflowApprovalViewSerializer
|
||||
permission_classes = (WorkflowApprovalPermission,)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
obj = self.get_object()
|
||||
if not request.user.can_access(models.WorkflowApproval, 'approve_or_deny', obj):
|
||||
raise PermissionDenied(detail=_("User does not have permission to approve or deny this workflow."))
|
||||
if obj.status != 'pending':
|
||||
return Response({"error": _("This workflow step has already been approved or denied.")}, status=status.HTTP_400_BAD_REQUEST)
|
||||
obj.approve(request)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class WorkflowApprovalDeny(RetrieveAPIView):
|
||||
model = models.WorkflowApproval
|
||||
serializer_class = serializers.WorkflowApprovalViewSerializer
|
||||
permission_classes = (WorkflowApprovalPermission,)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
obj = self.get_object()
|
||||
if not request.user.can_access(models.WorkflowApproval, 'approve_or_deny', obj):
|
||||
raise PermissionDenied(detail=_("User does not have permission to approve or deny this workflow."))
|
||||
if obj.status != 'pending':
|
||||
return Response({"error": _("This workflow step has already been approved or denied.")}, status=status.HTTP_400_BAD_REQUEST)
|
||||
obj.deny(request)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@@ -124,6 +124,7 @@ class ApiVersionRootView(APIView):
|
||||
data['activity_stream'] = reverse('api:activity_stream_list', request=request)
|
||||
data['workflow_job_templates'] = reverse('api:workflow_job_template_list', request=request)
|
||||
data['workflow_jobs'] = reverse('api:workflow_job_list', request=request)
|
||||
data['workflow_approvals'] = reverse('api:workflow_approval_list', request=request)
|
||||
data['workflow_job_template_nodes'] = reverse('api:workflow_job_template_node_list', request=request)
|
||||
data['workflow_job_nodes'] = reverse('api:workflow_job_node_list', request=request)
|
||||
return Response(data)
|
||||
|
||||
Reference in New Issue
Block a user