Add view to list documents by their workflow and by their
current workflow state. Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
@@ -120,6 +120,8 @@ Other changes
|
|||||||
controlled by the COMMON_TEST_FILE_HANDLES and COMMON_TEST_FILE_HANDLES settings.
|
controlled by the COMMON_TEST_FILE_HANDLES and COMMON_TEST_FILE_HANDLES settings.
|
||||||
|
|
||||||
- Add tool to launch all workflows. GitLab issue #355
|
- Add tool to launch all workflows. GitLab issue #355
|
||||||
|
- New workflow view that lists documents currently executing a workflow and
|
||||||
|
documents by their specific current workflow state.
|
||||||
|
|
||||||
Removals
|
Removals
|
||||||
--------
|
--------
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
from kombu import Exchange, Queue
|
from kombu import Exchange, Queue
|
||||||
|
|
||||||
from common import (
|
from common import (
|
||||||
MayanAppConfig, menu_facet, menu_object, menu_secondary, menu_setup,
|
MayanAppConfig, menu_facet, menu_main, menu_object, menu_secondary,
|
||||||
menu_sidebar, menu_tools
|
menu_setup, menu_sidebar, menu_tools
|
||||||
)
|
)
|
||||||
from common.widgets import two_state_template
|
from common.widgets import two_state_template
|
||||||
from mayan.celery import app
|
from mayan.celery import app
|
||||||
@@ -25,7 +25,9 @@ from .links import (
|
|||||||
link_setup_workflow_transitions, link_setup_workflow_transition_create,
|
link_setup_workflow_transitions, link_setup_workflow_transition_create,
|
||||||
link_setup_workflow_transition_delete, link_setup_workflow_transition_edit,
|
link_setup_workflow_transition_delete, link_setup_workflow_transition_edit,
|
||||||
link_tool_launch_all_workflows, link_workflow_instance_detail,
|
link_tool_launch_all_workflows, link_workflow_instance_detail,
|
||||||
link_workflow_instance_transition
|
link_workflow_instance_transition, link_workflow_document_list,
|
||||||
|
link_workflow_list, link_workflow_state_document_list,
|
||||||
|
link_workflow_state_list
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -47,7 +49,9 @@ class DocumentStatesApp(MayanAppConfig):
|
|||||||
Workflow = self.get_model('Workflow')
|
Workflow = self.get_model('Workflow')
|
||||||
WorkflowInstance = self.get_model('WorkflowInstance')
|
WorkflowInstance = self.get_model('WorkflowInstance')
|
||||||
WorkflowInstanceLogEntry = self.get_model('WorkflowInstanceLogEntry')
|
WorkflowInstanceLogEntry = self.get_model('WorkflowInstanceLogEntry')
|
||||||
|
WorkflowRuntimeProxy = self.get_model('WorkflowRuntimeProxy')
|
||||||
WorkflowState = self.get_model('WorkflowState')
|
WorkflowState = self.get_model('WorkflowState')
|
||||||
|
WorkflowStateRuntimeProxy = self.get_model('WorkflowStateRuntimeProxy')
|
||||||
WorkflowTransition = self.get_model('WorkflowTransition')
|
WorkflowTransition = self.get_model('WorkflowTransition')
|
||||||
|
|
||||||
SourceColumn(
|
SourceColumn(
|
||||||
@@ -135,6 +139,7 @@ class DocumentStatesApp(MayanAppConfig):
|
|||||||
menu_facet.bind_links(
|
menu_facet.bind_links(
|
||||||
links=(link_document_workflow_instance_list,), sources=(Document,)
|
links=(link_document_workflow_instance_list,), sources=(Document,)
|
||||||
)
|
)
|
||||||
|
menu_main.bind_links(links=(link_workflow_list,), position=10)
|
||||||
menu_object.bind_links(
|
menu_object.bind_links(
|
||||||
links=(
|
links=(
|
||||||
link_setup_workflow_states, link_setup_workflow_transitions,
|
link_setup_workflow_states, link_setup_workflow_transitions,
|
||||||
@@ -160,6 +165,16 @@ class DocumentStatesApp(MayanAppConfig):
|
|||||||
link_workflow_instance_transition
|
link_workflow_instance_transition
|
||||||
), sources=(WorkflowInstance,)
|
), sources=(WorkflowInstance,)
|
||||||
)
|
)
|
||||||
|
menu_object.bind_links(
|
||||||
|
links=(
|
||||||
|
link_workflow_document_list, link_workflow_state_list,
|
||||||
|
), sources=(WorkflowRuntimeProxy,)
|
||||||
|
)
|
||||||
|
menu_object.bind_links(
|
||||||
|
links=(
|
||||||
|
link_workflow_state_document_list,
|
||||||
|
), sources=(WorkflowStateRuntimeProxy,)
|
||||||
|
)
|
||||||
menu_secondary.bind_links(
|
menu_secondary.bind_links(
|
||||||
links=(link_setup_workflow_list, link_setup_workflow_create),
|
links=(link_setup_workflow_list, link_setup_workflow_create),
|
||||||
sources=(
|
sources=(
|
||||||
@@ -167,6 +182,12 @@ class DocumentStatesApp(MayanAppConfig):
|
|||||||
'document_states:setup_workflow_list'
|
'document_states:setup_workflow_list'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
menu_secondary.bind_links(
|
||||||
|
links=(link_workflow_list,),
|
||||||
|
sources=(
|
||||||
|
WorkflowRuntimeProxy,
|
||||||
|
)
|
||||||
|
)
|
||||||
menu_setup.bind_links(links=(link_setup_workflow_list,))
|
menu_setup.bind_links(links=(link_setup_workflow_list,))
|
||||||
menu_sidebar.bind_links(
|
menu_sidebar.bind_links(
|
||||||
links=(
|
links=(
|
||||||
|
|||||||
@@ -86,3 +86,21 @@ link_workflow_instance_transition = Link(
|
|||||||
view='document_states:workflow_instance_transition',
|
view='document_states:workflow_instance_transition',
|
||||||
args='resolved_object.pk'
|
args='resolved_object.pk'
|
||||||
)
|
)
|
||||||
|
link_workflow_document_list = Link(
|
||||||
|
permissions=(permission_workflow_view,), text=_('Workflow documents'),
|
||||||
|
view='document_states:workflow_document_list', args='resolved_object.pk'
|
||||||
|
)
|
||||||
|
link_workflow_list = Link(
|
||||||
|
permissions=(permission_workflow_view,), icon='fa fa-sitemap',
|
||||||
|
text=_('Workflows'), view='document_states:workflow_list'
|
||||||
|
)
|
||||||
|
link_workflow_state_document_list = Link(
|
||||||
|
permissions=(permission_workflow_view,),
|
||||||
|
text=_('State documents'), view='document_states:workflow_state_document_list',
|
||||||
|
args='resolved_object.pk'
|
||||||
|
)
|
||||||
|
link_workflow_state_list = Link(
|
||||||
|
permissions=(permission_workflow_view,),
|
||||||
|
text=_('States'), view='document_states:workflow_state_list',
|
||||||
|
args='resolved_object.pk'
|
||||||
|
)
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class Workflow(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
ordering = ('label',)
|
||||||
verbose_name = _('Workflow')
|
verbose_name = _('Workflow')
|
||||||
verbose_name_plural = _('Workflows')
|
verbose_name_plural = _('Workflows')
|
||||||
|
|
||||||
@@ -89,6 +90,7 @@ class WorkflowState(models.Model):
|
|||||||
return super(WorkflowState, self).save(*args, **kwargs)
|
return super(WorkflowState, self).save(*args, **kwargs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
ordering = ('label',)
|
||||||
unique_together = ('workflow', 'label')
|
unique_together = ('workflow', 'label')
|
||||||
verbose_name = _('Workflow state')
|
verbose_name = _('Workflow state')
|
||||||
verbose_name_plural = _('Workflow states')
|
verbose_name_plural = _('Workflow states')
|
||||||
@@ -114,6 +116,7 @@ class WorkflowTransition(models.Model):
|
|||||||
return self.label
|
return self.label
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
ordering = ('label',)
|
||||||
unique_together = (
|
unique_together = (
|
||||||
'workflow', 'label', 'origin_state', 'destination_state'
|
'workflow', 'label', 'origin_state', 'destination_state'
|
||||||
)
|
)
|
||||||
@@ -211,3 +214,17 @@ class WorkflowInstanceLogEntry(models.Model):
|
|||||||
def clean(self):
|
def clean(self):
|
||||||
if self.transition not in self.workflow_instance.get_transition_choices():
|
if self.transition not in self.workflow_instance.get_transition_choices():
|
||||||
raise ValidationError(_('Not a valid transition choice.'))
|
raise ValidationError(_('Not a valid transition choice.'))
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowRuntimeProxy(Workflow):
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
verbose_name = _('Workflow runtime proxy')
|
||||||
|
verbose_name_plural = _('Workflow runtime proxies')
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowStateRuntimeProxy(WorkflowState):
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
verbose_name = _('Workflow state runtime proxy')
|
||||||
|
verbose_name_plural = _('Workflow state runtime proxies')
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ from .views import (
|
|||||||
SetupWorkflowTransitionListView, SetupWorkflowTransitionCreateView,
|
SetupWorkflowTransitionListView, SetupWorkflowTransitionCreateView,
|
||||||
SetupWorkflowTransitionDeleteView, SetupWorkflowTransitionEditView,
|
SetupWorkflowTransitionDeleteView, SetupWorkflowTransitionEditView,
|
||||||
ToolLaunchAllWorkflows, WorkflowDocumentListView,
|
ToolLaunchAllWorkflows, WorkflowDocumentListView,
|
||||||
WorkflowInstanceDetailView, WorkflowInstanceTransitionView
|
WorkflowInstanceDetailView, WorkflowInstanceTransitionView,
|
||||||
|
WorkflowListView, WorkflowStateDocumentListView, WorkflowStateListView
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@@ -107,6 +108,27 @@ urlpatterns = [
|
|||||||
ToolLaunchAllWorkflows.as_view(),
|
ToolLaunchAllWorkflows.as_view(),
|
||||||
name='tool_launch_all_workflows'
|
name='tool_launch_all_workflows'
|
||||||
),
|
),
|
||||||
|
url(
|
||||||
|
r'all/$',
|
||||||
|
WorkflowListView.as_view(),
|
||||||
|
name='workflow_list'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^(?P<pk>\d+)/documents/$',
|
||||||
|
WorkflowDocumentListView.as_view(),
|
||||||
|
name='workflow_document_list'
|
||||||
|
),
|
||||||
|
|
||||||
|
url(
|
||||||
|
r'^(?P<pk>\d+)/states/$',
|
||||||
|
WorkflowStateListView.as_view(),
|
||||||
|
name='workflow_state_list'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^state/(?P<pk>\d+)/documents/$',
|
||||||
|
WorkflowStateDocumentListView.as_view(),
|
||||||
|
name='workflow_state_document_list'
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
api_urls = [
|
api_urls = [
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from __future__ import absolute_import, unicode_literals
|
|||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.core.urlresolvers import reverse, reverse_lazy
|
from django.core.urlresolvers import reverse, reverse_lazy
|
||||||
|
from django.db.models import F, Max
|
||||||
from django.db.utils import IntegrityError
|
from django.db.utils import IntegrityError
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
@@ -20,7 +21,10 @@ from .forms import (
|
|||||||
WorkflowForm, WorkflowInstanceTransitionForm, WorkflowStateForm,
|
WorkflowForm, WorkflowInstanceTransitionForm, WorkflowStateForm,
|
||||||
WorkflowTransitionForm
|
WorkflowTransitionForm
|
||||||
)
|
)
|
||||||
from .models import Workflow, WorkflowInstance, WorkflowState, WorkflowTransition
|
from .models import (
|
||||||
|
Workflow, WorkflowInstance, WorkflowInstanceLogEntry, WorkflowState,
|
||||||
|
WorkflowTransition, WorkflowRuntimeProxy, WorkflowStateRuntimeProxy
|
||||||
|
)
|
||||||
from .permissions import (
|
from .permissions import (
|
||||||
permission_workflow_create, permission_workflow_delete,
|
permission_workflow_create, permission_workflow_delete,
|
||||||
permission_workflow_edit, permission_workflow_transition,
|
permission_workflow_edit, permission_workflow_transition,
|
||||||
@@ -56,36 +60,10 @@ class DocumentWorkflowInstanceListView(SingleObjectListView):
|
|||||||
return self.get_document().workflows.all()
|
return self.get_document().workflows.all()
|
||||||
|
|
||||||
|
|
||||||
class WorkflowDocumentListView(DocumentListView):
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
|
||||||
self.workflow = get_object_or_404(Workflow, pk=self.kwargs['pk'])
|
|
||||||
|
|
||||||
AccessControlList.objects.check_access(
|
|
||||||
permissions=permission_workflow_view, user=request.user,
|
|
||||||
obj=self.workflow
|
|
||||||
)
|
|
||||||
|
|
||||||
return super(
|
|
||||||
WorkflowDocumentListView, self
|
|
||||||
).dispatch(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def get_document_queryset(self):
|
|
||||||
return Document.objects.filter(
|
|
||||||
document_type__in=self.workflow.document_types.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_extra_context(self):
|
|
||||||
return {
|
|
||||||
'hide_links': True,
|
|
||||||
'object': self.workflow,
|
|
||||||
'title': _('Documents with the workflow: %s') % self.workflow
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class WorkflowInstanceDetailView(SingleObjectListView):
|
class WorkflowInstanceDetailView(SingleObjectListView):
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
AccessControlList.objects.check_access(
|
AccessControlList.objects.check_access(
|
||||||
permissions=permission_workflow_view, users=request.user,
|
permissions=permission_workflow_view, user=request.user,
|
||||||
obj=self.get_workflow_instance().document
|
obj=self.get_workflow_instance().document
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -432,6 +410,121 @@ class SetupWorkflowTransitionEditView(SingleObjectEditView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowListView(SingleObjectListView):
|
||||||
|
view_permission = permission_workflow_view
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return WorkflowRuntimeProxy.objects.all()
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'hide_link': True,
|
||||||
|
'title': _('Workflows')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowDocumentListView(DocumentListView):
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
self.workflow = get_object_or_404(
|
||||||
|
WorkflowRuntimeProxy, pk=self.kwargs['pk']
|
||||||
|
)
|
||||||
|
|
||||||
|
AccessControlList.objects.check_access(
|
||||||
|
permissions=permission_workflow_view, user=request.user,
|
||||||
|
obj=self.workflow
|
||||||
|
)
|
||||||
|
|
||||||
|
return super(
|
||||||
|
WorkflowDocumentListView, self
|
||||||
|
).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_document_queryset(self):
|
||||||
|
return Document.objects.filter(workflows__workflow=self.workflow)
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'hide_links': True,
|
||||||
|
'object': self.workflow,
|
||||||
|
'title': _('Documents with the workflow: %s') % self.workflow
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowStateDocumentListView(DocumentListView):
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
self.workflow_state = get_object_or_404(
|
||||||
|
WorkflowStateRuntimeProxy, pk=self.kwargs['pk']
|
||||||
|
)
|
||||||
|
|
||||||
|
AccessControlList.objects.check_access(
|
||||||
|
permissions=permission_workflow_view, user=request.user,
|
||||||
|
obj=self.workflow_state.workflow
|
||||||
|
)
|
||||||
|
|
||||||
|
return super(
|
||||||
|
WorkflowStateDocumentListView, self
|
||||||
|
).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_document_queryset(self):
|
||||||
|
latest_entries = WorkflowInstanceLogEntry.objects.annotate(
|
||||||
|
max_datetime=Max(
|
||||||
|
'workflow_instance__log_entries__datetime'
|
||||||
|
)
|
||||||
|
).filter(
|
||||||
|
datetime=F('max_datetime')
|
||||||
|
)
|
||||||
|
|
||||||
|
state_latest_entries = latest_entries.filter(
|
||||||
|
transition__destination_state=self.workflow_state
|
||||||
|
)
|
||||||
|
|
||||||
|
return Document.objects.filter(
|
||||||
|
workflows__pk__in=state_latest_entries.values_list(
|
||||||
|
'workflow_instance', flat=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'hide_links': True,
|
||||||
|
'object': self.workflow_state,
|
||||||
|
'navigation_object_list': ('object', 'workflow'),
|
||||||
|
'workflow': WorkflowRuntimeProxy.objects.get(
|
||||||
|
pk=self.workflow_state.workflow.pk
|
||||||
|
),
|
||||||
|
'title': _(
|
||||||
|
'Documents in the workflow "%s", state "%s"'
|
||||||
|
) % (self.workflow_state.workflow, self.workflow_state)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowStateListView(SingleObjectListView):
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
AccessControlList.objects.check_access(
|
||||||
|
permissions=permission_workflow_view, user=request.user,
|
||||||
|
obj=self.get_workflow()
|
||||||
|
)
|
||||||
|
|
||||||
|
return super(
|
||||||
|
WorkflowStateListView, self
|
||||||
|
).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'hide_columns': True,
|
||||||
|
'hide_link': True,
|
||||||
|
'object': self.get_workflow(),
|
||||||
|
'title': _('States of workflow: %s') % self.get_workflow()
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return WorkflowStateRuntimeProxy.objects.filter(
|
||||||
|
workflow=self.get_workflow()
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_workflow(self):
|
||||||
|
return get_object_or_404(WorkflowRuntimeProxy, pk=self.kwargs['pk'])
|
||||||
|
|
||||||
|
|
||||||
class ToolLaunchAllWorkflows(ConfirmView):
|
class ToolLaunchAllWorkflows(ConfirmView):
|
||||||
extra_context = {
|
extra_context = {
|
||||||
'title': _('Launch all workflows?')
|
'title': _('Launch all workflows?')
|
||||||
|
|||||||
Reference in New Issue
Block a user