diff --git a/mayan/apps/document_states/__init__.py b/mayan/apps/document_states/__init__.py index 6c598a0ee9..502e8d39ba 100644 --- a/mayan/apps/document_states/__init__.py +++ b/mayan/apps/document_states/__init__.py @@ -9,13 +9,18 @@ from documents.models import Document from navigation.api import register_links, register_model_list_columns from project_setup.api import register_setup -from .models import Workflow, WorkflowInstance, WorkflowState, WorkflowTransition -from .links import (link_setup_workflow_create, link_setup_workflow_delete, - link_setup_workflow_edit, link_setup_workflow_list, - link_setup_workflow_states, link_setup_workflow_states_create, - link_setup_workflow_transitions, link_setup_workflow_transitions_create, - link_setup_workflow_document_types, - link_document_workflow_list) +from .models import ( + Workflow, WorkflowInstance, WorkflowInstanceLogEntry, WorkflowState, + WorkflowTransition +) +from .links import ( + link_document_workflow_instance_list, link_setup_workflow_create, + link_setup_workflow_delete, link_setup_workflow_edit, + link_setup_workflow_list, link_setup_workflow_states, + link_setup_workflow_states_create, link_setup_workflow_transitions, + link_setup_workflow_transitions_create, link_setup_workflow_document_types, + link_workflow_instance_detail, link_workflow_instance_transition +) @receiver(post_save, dispatch_uid='launch_workflow', sender=Document) @@ -49,6 +54,10 @@ register_model_list_columns(WorkflowInstance, [ 'name': _('Last transition'), 'attribute': 'get_last_transition' }, + { + 'name': _('Date and time'), + 'attribute': encapsulate(lambda workflow: getattr(workflow.get_last_log_entry(), 'datetime', _('None'))) + }, ]) register_model_list_columns(WorkflowTransition, [ @@ -62,7 +71,19 @@ register_model_list_columns(WorkflowTransition, [ }, ]) -register_links([Document], [link_document_workflow_list], menu_name='form_header') +register_model_list_columns(WorkflowInstanceLogEntry, [ + { + 'name': _('Date and time'), + 'attribute': 'datetime' + }, + { + 'name': _('Transition'), + 'attribute': 'transition' + }, +]) + +register_links([Document], [link_document_workflow_instance_list], menu_name='form_header') +register_links([WorkflowInstance], [link_workflow_instance_detail, link_workflow_instance_transition]) register_links([Workflow, 'document_states:setup_workflow_create', 'document_states:setup_workflow_list'], [link_setup_workflow_list, link_setup_workflow_create], menu_name='secondary_menu') register_links([Workflow], [link_setup_workflow_states, link_setup_workflow_transitions, link_setup_workflow_document_types, link_setup_workflow_edit, link_setup_workflow_delete]) register_links([Workflow], [link_setup_workflow_states_create, link_setup_workflow_transitions_create], menu_name='sidebar') diff --git a/mayan/apps/document_states/forms.py b/mayan/apps/document_states/forms.py index c2e4a32f9c..31693e99ef 100644 --- a/mayan/apps/document_states/forms.py +++ b/mayan/apps/document_states/forms.py @@ -3,7 +3,9 @@ from __future__ import unicode_literals from django import forms from django.utils.translation import ugettext_lazy as _ -from .models import WorkflowState, WorkflowTransition +from common.forms import DetailForm + +from .models import WorkflowState, WorkflowInstance, WorkflowTransition class WorkflowStateForm(forms.ModelForm): @@ -13,7 +15,27 @@ class WorkflowStateForm(forms.ModelForm): class WorkflowTransitionForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + workflow = kwargs.pop('workflow') + super(WorkflowTransitionForm, self).__init__(*args, **kwargs) + self.fields['origin_state'].queryset = self.fields['origin_state'].queryset.filter(workflow=workflow) + self.fields['destination_state'].queryset = self.fields['destination_state'].queryset.filter(workflow=workflow) + class Meta: - # TODO: restrict states to the ones of this workflow fields = ('label', 'origin_state', 'destination_state') model = WorkflowTransition + + +class WorkflowInstanceTransitionForm(forms.Form): + def __init__(self, *args, **kwargs): + workflow = kwargs.pop('workflow') + super(WorkflowInstanceTransitionForm, self).__init__(*args, **kwargs) + self.fields['transition'].choices = workflow.get_transition_choices().values_list('pk', 'label') + + transition = forms.ChoiceField(label=_('Transition')) + + +class WorkflowInstanceDetailForm(DetailForm): + class Meta: + model = WorkflowInstance + fields = ('workflow',) diff --git a/mayan/apps/document_states/links.py b/mayan/apps/document_states/links.py index fd9b87bcfe..7e48ccdc30 100644 --- a/mayan/apps/document_states/links.py +++ b/mayan/apps/document_states/links.py @@ -15,4 +15,6 @@ link_setup_workflow_transitions_create = {'text': _('Create transition'), 'view' link_setup_workflow_document_types = {'text': _('Document types'), 'view': 'document_states:setup_workflow_document_types', 'args': 'object.pk', 'famfam': 'layout'} -link_document_workflow_list = {'text': _('Workflows'), 'view': 'document_states:document_workflow_list', 'args': 'object.pk', 'famfam': 'table'} +link_document_workflow_instance_list = {'text': _('Workflows'), 'view': 'document_states:document_workflow_instance_list', 'args': 'object.pk', 'famfam': 'table'} +link_workflow_instance_detail = {'text': _('Detail'), 'view': 'document_states:workflow_instance_detail', 'args': 'workflow_instance.pk', 'famfam': 'table'} +link_workflow_instance_transition = {'text': _('Transition'), 'view': 'document_states:workflow_instance_transition', 'args': 'workflow_instance.pk', 'famfam': 'table_lightning'} diff --git a/mayan/apps/document_states/models.py b/mayan/apps/document_states/models.py index d5ba3fc94e..5f25b50f54 100644 --- a/mayan/apps/document_states/models.py +++ b/mayan/apps/document_states/models.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +from django.core.urlresolvers import reverse from django.db import models from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext as _ @@ -61,8 +62,8 @@ class WorkflowTransition(models.Model): workflow = models.ForeignKey(Workflow, related_name='transitions', verbose_name=_('Workflow')) label = models.CharField(max_length=255, verbose_name=_('Label')) - origin_state = models.ForeignKey(WorkflowState, related_name='origins', verbose_name=_('Origin state')) - destination_state = models.ForeignKey(WorkflowState, related_name='destinations', verbose_name=_('Destination state')) + origin_state = models.ForeignKey(WorkflowState, related_name='origin_transitions', verbose_name=_('Origin state')) + destination_state = models.ForeignKey(WorkflowState, related_name='destination_transitions', verbose_name=_('Destination state')) def __str__(self): return self.label @@ -78,9 +79,12 @@ class WorkflowInstance(models.Model): workflow = models.ForeignKey(Workflow, related_name='instances', verbose_name=_('Workflow')) document = models.ForeignKey(Document, related_name='workflows', verbose_name=_('Document')) + def get_absolute_url(self): + return reverse('document_states:workflow_instance_detail', args=[str(self.pk)]) + def do_transition(self, transition): try: - if transition in self.get_current_state().origins: + if transition in self.get_current_state().origin_transitions.all(): self.log_entries.create(transition=transition) except AttributeError: # No initial state has been set for this workflow @@ -94,12 +98,21 @@ class WorkflowInstance(models.Model): def get_last_transition(self): try: - return self.log_entries.order_by('datetime').last().transition + return self.get_last_log_entry().transition except AttributeError: return None + def get_last_log_entry(self): + try: + return self.log_entries.order_by('datetime').last() + except AttributeError: + return None + + def get_transition_choices(self): + return self.get_current_state().origin_transitions.all() + def __str__(self): - return self.workflow + return unicode(self.workflow) class Meta: unique_together = ('document', 'workflow') @@ -114,7 +127,7 @@ class WorkflowInstanceLogEntry(models.Model): transition = models.ForeignKey(WorkflowTransition, verbose_name=_('Transition')) def __str__(self): - return self.label + return unicode(self.transition) class Meta: verbose_name = _('Workflow instance log entry') diff --git a/mayan/apps/document_states/urls.py b/mayan/apps/document_states/urls.py index b49b2982cb..c881e26c1c 100644 --- a/mayan/apps/document_states/urls.py +++ b/mayan/apps/document_states/urls.py @@ -1,10 +1,12 @@ from django.conf.urls import patterns, url -from .views import (SetupWorkflowCreateView, SetupWorkflowDeleteView, - SetupWorkflowEditView, SetupWorkflowListView, - SetupWorkflowStateListView, SetupWorkflowStateCreateView, - SetupWorkflowTransitionListView, SetupWorkflowTransitionCreateView, - DocumentWorkflowListView) +from .views import ( + SetupWorkflowCreateView, SetupWorkflowDeleteView, SetupWorkflowEditView, + SetupWorkflowListView, SetupWorkflowStateListView, + SetupWorkflowStateCreateView, SetupWorkflowTransitionListView, + SetupWorkflowTransitionCreateView, DocumentWorkflowInstanceListView, + WorkflowInstanceDetailView, WorkflowInstanceTransitionView +) urlpatterns = patterns('', url(r'^setup/all/$', SetupWorkflowListView.as_view(), name='setup_workflow_list'), @@ -17,7 +19,9 @@ urlpatterns = patterns('', url(r'^setup/(?P\d+)/transitions/$', SetupWorkflowTransitionListView.as_view(), name='setup_workflow_transitions'), url(r'^setup/(?P\d+)/transitions/create/$', SetupWorkflowTransitionCreateView.as_view(), name='setup_workflow_transitions_create'), - url(r'^document/(?P\d+)/workflows/$', DocumentWorkflowListView.as_view(), name='document_workflow_list'), + url(r'^document/(?P\d+)/workflows/$', DocumentWorkflowInstanceListView.as_view(), name='document_workflow_instance_list'), + url(r'^document/workflows/(?P\d+)/$', WorkflowInstanceDetailView.as_view(), name='workflow_instance_detail'), + url(r'^document/workflows/(?P\d+)/transition/$', WorkflowInstanceTransitionView.as_view(), name='workflow_instance_transition'), ) urlpatterns += patterns('document_states.views', diff --git a/mayan/apps/document_states/views.py b/mayan/apps/document_states/views.py index 7e03e05433..de8d72c0b3 100644 --- a/mayan/apps/document_states/views.py +++ b/mayan/apps/document_states/views.py @@ -10,6 +10,7 @@ from django.shortcuts import render_to_response, get_object_or_404 from django.template import RequestContext from django.utils.http import urlencode from django.utils.translation import ugettext_lazy as _, ungettext +from django.views.generic import FormView from acls.models import AccessEntry from common.utils import encapsulate, generate_choices_w_labels @@ -20,21 +21,26 @@ from common.widgets import two_state_template from documents.models import Document from permissions.models import Permission -from .forms import WorkflowStateForm, WorkflowTransitionForm -from .models import Workflow -from .permissions import (PERMISSION_WORKFLOW_CREATE, PERMISSION_WORKFLOW_DELETE, - PERMISSION_WORKFLOW_EDIT, PERMISSION_WORKFLOW_VIEW, - PERMISSION_DOCUMENT_WORKFLOW_VIEW) +from .forms import ( + WorkflowInstanceDetailForm, WorkflowInstanceTransitionForm, + WorkflowStateForm, WorkflowTransitionForm +) +from .models import Workflow, WorkflowInstance +from .permissions import ( + PERMISSION_WORKFLOW_CREATE, PERMISSION_WORKFLOW_DELETE, + PERMISSION_WORKFLOW_EDIT, PERMISSION_WORKFLOW_VIEW, + PERMISSION_DOCUMENT_WORKFLOW_VIEW, PERMISSION_DOCUMENT_WORKFLOW_TRANSITION +) -class DocumentWorkflowListView(SingleObjectListView): +class DocumentWorkflowInstanceListView(SingleObjectListView): def dispatch(self, request, *args, **kwargs): try: Permission.objects.check_permissions(request.user, [PERMISSION_DOCUMENT_WORKFLOW_VIEW]) except PermissionDenied: - AccessEntry.objects.check_access(PERMISSION_DOCUMENT_WORKFLOW_VIEW, request.user, self.get_workflow()) + AccessEntry.objects.check_access(PERMISSION_DOCUMENT_WORKFLOW_VIEW, request.user, self.get_document()) - return super(DocumentWorkflowListView, self).dispatch(request, *args, **kwargs) + return super(DocumentWorkflowInstanceListView, self).dispatch(request, *args, **kwargs) def get_document(self): return get_object_or_404(Document, pk=self.kwargs['pk']) @@ -43,18 +49,123 @@ class DocumentWorkflowListView(SingleObjectListView): return self.get_document().workflows.all() def get_context_data(self, **kwargs): - context = super(DocumentWorkflowListView, self).get_context_data(**kwargs) + context = super(DocumentWorkflowInstanceListView, self).get_context_data(**kwargs) context.update( { 'hide_link': True, 'object': self.get_document(), - 'title': _('Workflows of document: %s') % self.get_document() + 'title': _('Workflows of document: %s') % self.get_document(), + 'list_object_variable_name': 'workflow_instance', } ) return context +class WorkflowInstanceDetailView(SingleObjectListView): + template_name = 'main/generic_multi_subtemplates.html' + + def dispatch(self, request, *args, **kwargs): + try: + Permission.objects.check_permissions(request.user, [PERMISSION_DOCUMENT_WORKFLOW_VIEW]) + except PermissionDenied: + AccessEntry.objects.check_access(PERMISSION_DOCUMENT_WORKFLOW_VIEW, request.user, self.get_workflow_instance().document) + + return super(WorkflowInstanceDetailView, self).dispatch(request, *args, **kwargs) + + def get_workflow_instance(self): + return get_object_or_404(WorkflowInstance, pk=self.kwargs['pk']) + + def get_queryset(self): + return self.get_workflow_instance().log_entries.all() + + def get_context_data(self, **kwargs): + form = WorkflowInstanceDetailForm(instance=self.get_workflow_instance(), extra_fields=[ + {'label': _('Current state'), 'field': 'get_current_state'}, + {'label': _('Last transition'), 'field': 'get_last_transition'}, + ] + ) + + context = { + 'object': self.get_workflow_instance().document, + 'workflow_instance': self.get_workflow_instance(), + 'navigation_object_list': [ + {'object': 'object', 'name': _('Index')}, + {'object': 'workflow_instance', 'name': _('Node')} + ], + 'title': _('Detail of workflow: %(workflow)s - %(document)s') % { + 'workflow': self.get_workflow_instance(), 'document': self.get_workflow_instance().document + }, + 'subtemplates_list': [ + { + 'name': 'main/generic_detail_subtemplate.html', + 'context': { + 'form': form, + } + }, + { + 'name': 'main/generic_list_subtemplate.html', + 'context': { + 'object_list': self.get_queryset(), + 'title': _('Log entries'), + 'hide_object': True, + } + } + ] + } + + return context + + +class WorkflowInstanceTransitionView(FormView): + form_class = WorkflowInstanceTransitionForm + template_name = 'main/generic_form.html' + + def dispatch(self, request, *args, **kwargs): + try: + Permission.objects.check_permissions(request.user, [PERMISSION_DOCUMENT_WORKFLOW_TRANSITION]) + except PermissionDenied: + AccessEntry.objects.check_access(PERMISSION_DOCUMENT_WORKFLOW_TRANSITION, request.user, self.get_workflow_instance().document) + + return super(WorkflowInstanceTransitionView, self).dispatch(request, *args, **kwargs) + + def form_valid(self, form): + transition = self.get_workflow_instance().workflow.transitions.get(pk=form.cleaned_data['transition']) + self.get_workflow_instance().do_transition(transition) + return HttpResponseRedirect(self.get_success_url()) + + def get_form_kwargs(self): + kwargs = super(WorkflowInstanceTransitionView, self).get_form_kwargs() + kwargs['workflow'] = self.get_workflow_instance() + return kwargs + + def get_workflow_instance(self): + return get_object_or_404(WorkflowInstance, pk=self.kwargs['pk']) + + def get_context_data(self, **kwargs): + context = super(WorkflowInstanceTransitionView, self).get_context_data(**kwargs) + + context.update( + { + 'object': self.get_workflow_instance().document, + 'workflow_instance': self.get_workflow_instance(), + 'navigation_object_list': [ + {'object': 'object', 'name': _('Index')}, + {'object': 'workflow_instance', 'name': _('Node')} + ], + 'title': _('Do transition for workflow: %s') % self.get_workflow_instance(), + 'submit_label': _('Submit'), + } + ) + + return context + + def get_success_url(self): + return self.get_workflow_instance().get_absolute_url() + + +# Setup + class SetupWorkflowListView(SingleObjectListView): extra_context = { 'title': _('Workflows'), @@ -200,6 +311,11 @@ class SetupWorkflowTransitionCreateView(SingleObjectCreateView): ) return context + def get_form_kwargs(self): + kwargs = super(SetupWorkflowTransitionCreateView, self).get_form_kwargs() + kwargs['workflow'] = self.get_workflow() + return kwargs + def get_workflow(self): return get_object_or_404(Workflow, pk=self.kwargs['pk']) diff --git a/mayan/apps/main/templates/main/generic_multi_subtemplates.html b/mayan/apps/main/templates/main/generic_multi_subtemplates.html new file mode 100644 index 0000000000..f2299d7f35 --- /dev/null +++ b/mayan/apps/main/templates/main/generic_multi_subtemplates.html @@ -0,0 +1,54 @@ +{% extends 'main/base.html' %} + +{% load subtemplates_tags %} + +{% block title %} :: {% include 'main/calculate_form_title.html' %}{% endblock %} + +{% block sidebar %} + {% for subtemplate in sidebar_subtemplates_list %} + {% if subtemplate.form %} + {% render_subtemplate subtemplate.name subtemplate.context as rendered_subtemplate %} +
+ {{ rendered_subtemplate }} +
+ {% else %} + {% render_subtemplate subtemplate.name subtemplate.context as rendered_subtemplate %} + {{ rendered_subtemplate }} + {% endif %} + {% if subtemplate.grid_clear or not subtemplate.grid %} + {% endif %} + {% endfor %} +{% endblock %} + +{% block content %} + {% if main_title %} +
+

{{ main_title }}

+
+ {% endif %} + +
+ + {% for subtemplate in subtemplates_list %} + + {% if subtemplate.grid %} +
+ {% else %} +
+ {% endif %} + {% if subtemplate.form %} + {% render_subtemplate subtemplate.name subtemplate.context as rendered_subtemplate %} +
+ {{ rendered_subtemplate }} +
+ {% else %} + {% render_subtemplate subtemplate.name subtemplate.context as rendered_subtemplate %} + {{ rendered_subtemplate }} + {% endif %} +
+ {% if subtemplate.grid_clear or not subtemplate.grid %} +
+ {% endif %} + {% endfor %} +
+{% endblock %}