Add view for associating a workflow to a document type, add support to auto launch a workflow instance when a document is created, allow associating a many workflows to a document, add view to display a document's workflows instances

This commit is contained in:
Roberto Rosario
2015-01-13 06:02:42 -04:00
parent 8ff3747a15
commit b2453ee01e
8 changed files with 231 additions and 25 deletions

View File

@@ -1,16 +1,28 @@
from __future__ import unicode_literals
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.translation import ugettext as _
from common.utils import encapsulate
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, WorkflowState, WorkflowTransition
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_transitions, link_setup_workflow_transitions_create,
link_setup_workflow_document_types,
link_document_workflow_list)
@receiver(post_save, dispatch_uid='launch_workflow', sender=Document)
def launch_workflow(sender, instance, created, **kwargs):
if created:
Workflow.objects.launch_for(instance)
register_setup(link_setup_workflow_list)
@@ -28,6 +40,17 @@ register_model_list_columns(WorkflowState, [
},
])
register_model_list_columns(WorkflowInstance, [
{
'name': _('Current state'),
'attribute': 'get_current_state'
},
{
'name': _('Last transition'),
'attribute': 'get_last_transition'
},
])
register_model_list_columns(WorkflowTransition, [
{
'name': _('Origin state'),
@@ -39,6 +62,7 @@ register_model_list_columns(WorkflowTransition, [
},
])
register_links([Document], [link_document_workflow_list], menu_name='form_header')
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_edit, link_setup_workflow_delete])
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')

View File

@@ -1,6 +1,6 @@
from django.contrib import admin
from .models import Workflow, WorkflowState, WorkflowTransition
from .models import Workflow, WorkflowInstance, WorkflowState, WorkflowTransition
class WorkflowStateInline(admin.TabularInline):
@@ -15,4 +15,9 @@ class WorkflowAdmin(admin.ModelAdmin):
inlines = [WorkflowStateInline, WorkflowTransitionInline]
class WorkflowInstanceAdmin(admin.ModelAdmin):
list_display = ('workflow', 'document')
admin.site.register(Workflow, WorkflowAdmin)
admin.site.register(WorkflowInstance, WorkflowInstanceAdmin)

View File

@@ -12,3 +12,7 @@ link_setup_workflow_states_create = {'text': _('Create state'), 'view': 'documen
link_setup_workflow_transitions = {'text': _('Transitions'), 'view': 'document_states:setup_workflow_transitions', 'args': 'object.pk', 'famfam': 'lightning'}
link_setup_workflow_transitions_create = {'text': _('Create transition'), 'view': 'document_states:setup_workflow_transitions_create', 'args': 'object.pk', 'famfam': 'lightning_add'}
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'}

View File

@@ -0,0 +1,9 @@
from __future__ import unicode_literals
from django.db import models
class WorkflowManager(models.Manager):
def launch_for(self, document):
for workflow in document.document_type.workflows.all():
workflow.launch_for(document)

View File

@@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Removing unique constraint on 'DocumentTypeWorkflow', fields ['document_type', 'workflow']
db.delete_unique(u'document_states_documenttypeworkflow', ['document_type_id', 'workflow_id'])
# Deleting model 'DocumentTypeWorkflow'
db.delete_table(u'document_states_documenttypeworkflow')
# Adding M2M table for field document_types on 'Workflow'
m2m_table_name = db.shorten_name(u'document_states_workflow_document_types')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('workflow', models.ForeignKey(orm[u'document_states.workflow'], null=False)),
('documenttype', models.ForeignKey(orm[u'documents.documenttype'], null=False))
))
db.create_unique(m2m_table_name, ['workflow_id', 'documenttype_id'])
def backwards(self, orm):
# Adding model 'DocumentTypeWorkflow'
db.create_table(u'document_states_documenttypeworkflow', (
('document_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['documents.DocumentType'])),
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('workflow', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['document_states.Workflow'])),
))
db.send_create_signal(u'document_states', ['DocumentTypeWorkflow'])
# Adding unique constraint on 'DocumentTypeWorkflow', fields ['document_type', 'workflow']
db.create_unique(u'document_states_documenttypeworkflow', ['document_type_id', 'workflow_id'])
# Removing M2M table for field document_types on 'Workflow'
db.delete_table(db.shorten_name(u'document_states_workflow_document_types'))
models = {
u'document_states.workflow': {
'Meta': {'object_name': 'Workflow'},
'document_types': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['documents.DocumentType']", 'symmetrical': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'label': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
},
u'document_states.workflowinstance': {
'Meta': {'unique_together': "((u'document', u'workflow'),)", 'object_name': 'WorkflowInstance'},
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['documents.Document']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'workflow': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['document_states.Workflow']"})
},
u'document_states.workflowinstancelogentry': {
'Meta': {'object_name': 'WorkflowInstanceLogEntry'},
'datetime': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'transition': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['document_states.WorkflowTransition']"}),
'workflow_instance': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'log_entries'", 'to': u"orm['document_states.WorkflowInstance']"})
},
u'document_states.workflowstate': {
'Meta': {'unique_together': "((u'workflow', u'label'),)", 'object_name': 'WorkflowState'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'initial': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'states'", 'to': u"orm['document_states.Workflow']"})
},
u'document_states.workflowtransition': {
'Meta': {'unique_together': "((u'workflow', u'label', u'origin_state', u'destination_state'),)", 'object_name': 'WorkflowTransition'},
'destination_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'destinations'", 'to': u"orm['document_states.WorkflowState']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'origin_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'origins'", 'to': u"orm['document_states.WorkflowState']"}),
'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'transitions'", 'to': u"orm['document_states.Workflow']"})
},
u'documents.document': {
'Meta': {'ordering': "['-date_added']", 'object_name': 'Document'},
'date_added': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'document_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'documents'", 'to': u"orm['documents.DocumentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'label': ('django.db.models.fields.CharField', [], {'default': "u'Uninitialized document'", 'max_length': '255', 'db_index': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'default': "u'eng'", 'max_length': '8'}),
'uuid': ('django.db.models.fields.CharField', [], {'default': "u'fbd029e5-0f29-4863-b72c-e8cd1373b9f3'", 'max_length': '48'})
},
u'documents.documenttype': {
'Meta': {'ordering': "['name']", 'object_name': 'DocumentType'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'}),
'ocr': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
}
}
complete_apps = ['document_states']

View File

@@ -6,20 +6,31 @@ from django.utils.translation import ugettext as _
from documents.models import Document, DocumentType
from .managers import WorkflowManager
@python_2_unicode_compatible
class Workflow(models.Model):
label = models.CharField(max_length=255, unique=True, verbose_name=_('Label'))
document_types = models.ManyToManyField(DocumentType, related_name='workflows', verbose_name=_('Document types'))
objects = WorkflowManager()
def __str__(self):
return self.label
def get_document_types_not_in_workflow(self):
return DocumentType.objects.exclude(pk__in=self.document_types.all())
def get_initial_state(self):
try:
return self.states.get(initial=True)
except self.states.model.DoesNotExist:
return None
def launch_for(self, document):
self.instances.create(document=document)
class Meta:
verbose_name = _('Workflow')
verbose_name_plural = _('Workflows')
@@ -62,24 +73,10 @@ class WorkflowTransition(models.Model):
verbose_name_plural = _('Workflow transitions')
@python_2_unicode_compatible
class DocumentTypeWorkflow(models.Model):
document_type = models.ForeignKey(DocumentType, verbose_name=_('Document type'))
workflow = models.ForeignKey(Workflow, verbose_name=_('Workflow'))
def __str__(self):
return self.label
class Meta:
unique_together = ('document_type', 'workflow')
verbose_name = _('Document type workflow')
verbose_name_plural = _('Document type workflow')
@python_2_unicode_compatible
class WorkflowInstance(models.Model):
workflow = models.ForeignKey(Workflow, verbose_name=_('Workflow'))
document = models.ForeignKey(Document, verbose_name=_('Document'))
workflow = models.ForeignKey(Workflow, related_name='instances', verbose_name=_('Workflow'))
document = models.ForeignKey(Document, related_name='workflows', verbose_name=_('Document'))
def do_transition(self, transition):
try:
@@ -91,12 +88,18 @@ class WorkflowInstance(models.Model):
def get_current_state(self):
try:
return self.log_entries.order_by('datetime').last().transition.destination_state
return self.get_last_transition().destination_state
except AttributeError:
return self.workflow.get_initial_state()
def get_last_transition(self):
try:
return self.log_entries.order_by('datetime').last().transition
except AttributeError:
return None
def __str__(self):
return self.label
return self.workflow
class Meta:
unique_together = ('document', 'workflow')

View File

@@ -3,7 +3,8 @@ from django.conf.urls import patterns, url
from .views import (SetupWorkflowCreateView, SetupWorkflowDeleteView,
SetupWorkflowEditView, SetupWorkflowListView,
SetupWorkflowStateListView, SetupWorkflowStateCreateView,
SetupWorkflowTransitionListView, SetupWorkflowTransitionCreateView)
SetupWorkflowTransitionListView, SetupWorkflowTransitionCreateView,
DocumentWorkflowListView)
urlpatterns = patterns('',
url(r'^setup/all/$', SetupWorkflowListView.as_view(), name='setup_workflow_list'),
@@ -15,4 +16,12 @@ urlpatterns = patterns('',
url(r'^setup/(?P<pk>\d+)/transitions/$', SetupWorkflowTransitionListView.as_view(), name='setup_workflow_transitions'),
url(r'^setup/(?P<pk>\d+)/transitions/create/$', SetupWorkflowTransitionCreateView.as_view(), name='setup_workflow_transitions_create'),
url(r'^document/(?P<pk>\d+)/workflows/$', DocumentWorkflowListView.as_view(), name='document_workflow_list'),
)
urlpatterns += patterns('document_states.views',
url(r'^setup/(?P<pk>\d+)/document_types/$', 'setup_workflow_document_types', name='setup_workflow_document_types'),
)

View File

@@ -12,13 +12,47 @@ from django.utils.http import urlencode
from django.utils.translation import ugettext_lazy as _, ungettext
from acls.models import AccessEntry
from common.utils import encapsulate, generate_choices_w_labels
from common.views import (SingleObjectCreateView, SingleObjectDeleteView,
SingleObjectEditView, SingleObjectListView)
SingleObjectEditView, SingleObjectListView,
assign_remove)
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
from .permissions import (PERMISSION_WORKFLOW_CREATE, PERMISSION_WORKFLOW_DELETE,
PERMISSION_WORKFLOW_EDIT, PERMISSION_WORKFLOW_VIEW,
PERMISSION_DOCUMENT_WORKFLOW_VIEW)
class DocumentWorkflowListView(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())
return super(DocumentWorkflowListView, self).dispatch(request, *args, **kwargs)
def get_document(self):
return get_object_or_404(Document, pk=self.kwargs['pk'])
def get_queryset(self):
return self.get_document().workflows.all()
def get_context_data(self, **kwargs):
context = super(DocumentWorkflowListView, self).get_context_data(**kwargs)
context.update(
{
'hide_link': True,
'object': self.get_document(),
'title': _('Workflows of document: %s') % self.get_document()
}
)
return context
class SetupWorkflowListView(SingleObjectListView):
@@ -185,3 +219,25 @@ class SetupWorkflowTransitionCreateView(SingleObjectCreateView):
return super(SetupWorkflowTransitionCreateView, self).form_invalid(form)
else:
return HttpResponseRedirect(self.get_success_url())
def setup_workflow_document_types(request, pk):
workflow = get_object_or_404(Workflow, pk=pk)
try:
Permission.objects.check_permissions(request.user, [PERMISSION_WORKFLOW_EDIT])
except PermissionDenied:
AccessEntry.objects.check_access(PERMISSION_WORKFLOW_EDIT, request.user, workflow)
return assign_remove(
request,
left_list=lambda: generate_choices_w_labels(workflow.get_document_types_not_in_workflow(), display_object_type=False),
right_list=lambda: generate_choices_w_labels(workflow.document_types.all(), display_object_type=False),
add_method=lambda x: workflow.document_types.add(x),
remove_method=lambda x: workflow.document_types.remove(x),
decode_content_type=True,
extra_context={
'main_title': _(u'Document types assigned the workflow: %s') % workflow,
'object': workflow,
}
)