diff --git a/apps/workflows/__init__.py b/apps/workflows/__init__.py index e69de29bb2..dd31e1196e 100644 --- a/apps/workflows/__init__.py +++ b/apps/workflows/__init__.py @@ -0,0 +1,19 @@ +from __future__ import absolute_import + +from django.utils.translation import ugettext_lazy as _ + +from navigation.api import register_links, register_multi_item_links +from project_setup.api import register_setup + +from .permissions import PERMISSION_WORKFLOW_SETUP_VIEW + +setup_workflow_link = {'text': _(u'workflows'), 'view': 'setup_workflow_list', 'icon': 'chart_organisation.png', 'permissions': [PERMISSION_WORKFLOW_SETUP_VIEW]} + +setup_workflow_list_link = {'text': _(u'workflow list'), 'view': 'setup_workflow_list', 'famfam': 'chart_organisation', 'permissions': [PERMISSION_WORKFLOW_SETUP_VIEW]} + +#register_links(User, [user_edit, user_set_password, user_delete]) +register_links(['setup_workflow_list'], [setup_workflow_link], menu_name=u'secondary_menu') +#register_multi_item_links(['user_list'], [user_multiple_set_password, user_multiple_delete]) + + +register_setup(setup_workflow_link) diff --git a/apps/workflows/literals.py b/apps/workflows/literals.py new file mode 100644 index 0000000000..8811669442 --- /dev/null +++ b/apps/workflows/literals.py @@ -0,0 +1,9 @@ +from django.utils.translation import ugettext_lazy as _ + +OPERAND_OR = 'or' +OPERAND_AND = 'and' + +OPERAND_CHOICES = ( + (OPERAND_OR, _(u'or')), + (OPERAND_AND, _(u'and')) +) diff --git a/apps/workflows/migrations/0001_initial.py b/apps/workflows/migrations/0001_initial.py new file mode 100644 index 0000000000..015bc09db3 --- /dev/null +++ b/apps/workflows/migrations/0001_initial.py @@ -0,0 +1,202 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'Ability' + db.create_table('workflows_ability', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('label', self.gf('django.db.models.fields.CharField')(unique=True, max_length=128)), + ('description', self.gf('django.db.models.fields.TextField')(blank=True)), + )) + db.send_create_signal('workflows', ['Ability']) + + # Adding model 'Workflow' + db.create_table('workflows_workflow', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('label', self.gf('django.db.models.fields.CharField')(unique=True, max_length=128)), + ('initial_state', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='workflow_initial_state', null=True, to=orm['workflows.WorkflowState'])), + ('description', self.gf('django.db.models.fields.TextField')(blank=True)), + )) + db.send_create_signal('workflows', ['Workflow']) + + # Adding model 'State' + db.create_table('workflows_state', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('label', self.gf('django.db.models.fields.CharField')(max_length=128)), + ('description', self.gf('django.db.models.fields.TextField')(blank=True)), + )) + db.send_create_signal('workflows', ['State']) + + # Adding model 'Transition' + db.create_table('workflows_transition', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('label', self.gf('django.db.models.fields.CharField')(max_length=128)), + ('description', self.gf('django.db.models.fields.TextField')(blank=True)), + )) + db.send_create_signal('workflows', ['Transition']) + + # Adding model 'WorkflowState' + db.create_table('workflows_workflowstate', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('workflow', self.gf('django.db.models.fields.related.ForeignKey')(related_name='workflow_state_workflow', to=orm['workflows.Workflow'])), + ('state', self.gf('django.db.models.fields.related.ForeignKey')(related_name='workflow_state_state', to=orm['workflows.State'])), + ('description', self.gf('django.db.models.fields.TextField')(blank=True)), + )) + db.send_create_signal('workflows', ['WorkflowState']) + + # Adding model 'WorkflowStateAbilityGrant' + db.create_table('workflows_workflowstateabilitygrant', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('workflow_state', self.gf('django.db.models.fields.related.ForeignKey')(related_name='workflow_state_ability', to=orm['workflows.WorkflowState'])), + ('content_type', self.gf('django.db.models.fields.related.ForeignKey')(related_name='workflow_state_ability_object', to=orm['contenttypes.ContentType'])), + ('object_id', self.gf('django.db.models.fields.PositiveIntegerField')()), + )) + db.send_create_signal('workflows', ['WorkflowStateAbilityGrant']) + + # Adding model 'WorkflowStateTransition' + db.create_table('workflows_workflowstatetransition', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('workflow_state_source', self.gf('django.db.models.fields.related.ForeignKey')(related_name='workflow_state_transition_source', to=orm['workflows.WorkflowState'])), + ('transition', self.gf('django.db.models.fields.related.ForeignKey')(related_name='workflow_state_transition', to=orm['workflows.Transition'])), + ('workflow_state_destination', self.gf('django.db.models.fields.related.ForeignKey')(related_name='workflow_state_transition_destination', to=orm['workflows.WorkflowState'])), + ('description', self.gf('django.db.models.fields.TextField')(blank=True)), + )) + db.send_create_signal('workflows', ['WorkflowStateTransition']) + + # Adding model 'WorkflowStateTransitionAbility' + db.create_table('workflows_workflowstatetransitionability', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('attribute_comparison_operand', self.gf('django.db.models.fields.CharField')(default='and', max_length=8)), + ('negate', self.gf('django.db.models.fields.BooleanField')(default=False)), + ('ability', self.gf('django.db.models.fields.related.ForeignKey')(related_name='workflow_state_transition_ability', to=orm['workflows.Ability'])), + ('description', self.gf('django.db.models.fields.TextField')(blank=True)), + )) + db.send_create_signal('workflows', ['WorkflowStateTransitionAbility']) + + # Adding model 'WorkflowInstance' + db.create_table('workflows_workflowinstance', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('workflow', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['workflows.Workflow'])), + ('content_type', self.gf('django.db.models.fields.related.ForeignKey')(related_name='workflow_instance_object', to=orm['contenttypes.ContentType'])), + ('object_id', self.gf('django.db.models.fields.PositiveIntegerField')()), + ('workflow_state', self.gf('django.db.models.fields.related.ForeignKey')(related_name='workflow_instance_state', to=orm['workflows.WorkflowState'])), + )) + db.send_create_signal('workflows', ['WorkflowInstance']) + + # Adding unique constraint on 'WorkflowInstance', fields ['content_type', 'object_id', 'workflow'] + db.create_unique('workflows_workflowinstance', ['content_type_id', 'object_id', 'workflow_id']) + + + def backwards(self, orm): + + # Removing unique constraint on 'WorkflowInstance', fields ['content_type', 'object_id', 'workflow'] + db.delete_unique('workflows_workflowinstance', ['content_type_id', 'object_id', 'workflow_id']) + + # Deleting model 'Ability' + db.delete_table('workflows_ability') + + # Deleting model 'Workflow' + db.delete_table('workflows_workflow') + + # Deleting model 'State' + db.delete_table('workflows_state') + + # Deleting model 'Transition' + db.delete_table('workflows_transition') + + # Deleting model 'WorkflowState' + db.delete_table('workflows_workflowstate') + + # Deleting model 'WorkflowStateAbilityGrant' + db.delete_table('workflows_workflowstateabilitygrant') + + # Deleting model 'WorkflowStateTransition' + db.delete_table('workflows_workflowstatetransition') + + # Deleting model 'WorkflowStateTransitionAbility' + db.delete_table('workflows_workflowstatetransitionability') + + # Deleting model 'WorkflowInstance' + db.delete_table('workflows_workflowinstance') + + + models = { + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'workflows.ability': { + 'Meta': {'object_name': 'Ability'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}) + }, + 'workflows.state': { + 'Meta': {'object_name': 'State'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'workflows.transition': { + 'Meta': {'object_name': 'Transition'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'workflows.workflow': { + 'Meta': {'object_name': 'Workflow'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'initial_state': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_initial_state'", 'null': 'True', 'to': "orm['workflows.WorkflowState']"}), + 'label': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}) + }, + 'workflows.workflowinstance': { + 'Meta': {'unique_together': "(('content_type', 'object_id', 'workflow'),)", 'object_name': 'WorkflowInstance'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'workflow_instance_object'", 'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'workflow': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.Workflow']"}), + 'workflow_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'workflow_instance_state'", 'to': "orm['workflows.WorkflowState']"}) + }, + 'workflows.workflowstate': { + 'Meta': {'object_name': 'WorkflowState'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'workflow_state_state'", 'to': "orm['workflows.State']"}), + 'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'workflow_state_workflow'", 'to': "orm['workflows.Workflow']"}) + }, + 'workflows.workflowstateabilitygrant': { + 'Meta': {'object_name': 'WorkflowStateAbilityGrant'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'workflow_state_ability_object'", 'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'workflow_state': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'workflow_state_ability'", 'to': "orm['workflows.WorkflowState']"}) + }, + 'workflows.workflowstatetransition': { + 'Meta': {'object_name': 'WorkflowStateTransition'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'transition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'workflow_state_transition'", 'to': "orm['workflows.Transition']"}), + 'workflow_state_destination': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'workflow_state_transition_destination'", 'to': "orm['workflows.WorkflowState']"}), + 'workflow_state_source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'workflow_state_transition_source'", 'to': "orm['workflows.WorkflowState']"}) + }, + 'workflows.workflowstatetransitionability': { + 'Meta': {'object_name': 'WorkflowStateTransitionAbility'}, + 'ability': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'workflow_state_transition_ability'", 'to': "orm['workflows.Ability']"}), + 'attribute_comparison_operand': ('django.db.models.fields.CharField', [], {'default': "'and'", 'max_length': '8'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'negate': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + } + } + + complete_apps = ['workflows'] diff --git a/apps/workflows/migrations/__init__.py b/apps/workflows/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/workflows/models.py b/apps/workflows/models.py index 71a8362390..6824cd61d9 100644 --- a/apps/workflows/models.py +++ b/apps/workflows/models.py @@ -1,3 +1,144 @@ -from django.db import models +from __future__ import absolute_import -# Create your models here. +from django.db import models +from django.contrib.contenttypes import generic +from django.contrib.contenttypes.models import ContentType +from django.utils.translation import ugettext_lazy as _ + +from permissions.models import StoredPermission + +from .literals import OPERAND_CHOICES, OPERAND_AND + + +class Ability(models.Model): + label = models.CharField(max_length=128, unique=True, verbose_name = _(u'label')) + description = models.TextField(blank=True, verbose_name=_(u'description')) + + def __unicode__(self): + return self.label + + class Meta: + verbose_name = _(u'ability') + verbose_name_plural = _(u'abilities') + + +class Workflow(models.Model): + label = models.CharField(max_length=128, unique=True, verbose_name = _(u'label')) + initial_state = models.ForeignKey('WorkflowState', related_name='workflow_initial_state', blank=True, null=True, verbose_name=_(u'initial state')) + description = models.TextField(blank=True, verbose_name=_(u'description')) + + def __unicode__(self): + return self.label + + class Meta: + verbose_name = _(u'workflow') + verbose_name_plural = _(u'workflows') + + +class State(models.Model): + label = models.CharField(max_length=128, verbose_name=_(u'label')) + description = models.TextField(blank=True, verbose_name=_(u'description')) + + def __unicode__(self): + #return '%s (%s)' % (self.name, self.workflow.name) + return self.label + + class Meta: + verbose_name = _(u'state') + verbose_name_plural = _(u'states') + + +class Transition(models.Model): + label = models.CharField(max_length=128, verbose_name=_(u'label')) + description = models.TextField(blank=True, verbose_name=_(u'description')) + + def __unicode__(self): + #return '%s (%s)' % (self.name, self.workflow.name) + return self.label + + class Meta: + verbose_name = _(u'transition') + verbose_name_plural = _(u'transitions') + + +class WorkflowState(models.Model): + workflow = models.ForeignKey(Workflow, related_name='workflow_state_workflow', verbose_name=_(u'workflow')) + state = models.ForeignKey(State, related_name='workflow_state_state', verbose_name=_(u'state')) + description = models.TextField(blank=True, verbose_name=_(u'description')) + + def __unicode__(self): + #return '%s (%s)' % (self.label, self.workflow.label) + return unicode(self.state) + + class Meta: + verbose_name = _(u'workflow state') + verbose_name_plural = _(u'workflows states') + + +class WorkflowStateAbilityGrant(models.Model): + workflow_state = models.ForeignKey(WorkflowState, related_name='workflow_state_ability', verbose_name=_(u'workflow state')) + content_type = models.ForeignKey(ContentType, related_name='workflow_state_ability_object')#, blank=True, null=True) + object_id = models.PositiveIntegerField()#blank=True, null=True) + content_object = generic.GenericForeignKey(ct_field='content_type', fk_field='object_id') + + def __unicode__(self): + return unicode(self.content_object) + + class Meta: + verbose_name = _(u'workflow state ability grant') + verbose_name_plural = _(u'workflows states ability grant') + +#TODO: WorkflowStateACLEntry +#WorkflowState +#Actor +#Object +#Permission (s) + + +#TODO: WorkflowStateAlarm +#label +#timedate +#interval + + +class WorkflowStateTransition(models.Model): + workflow_state_source = models.ForeignKey(WorkflowState, related_name='workflow_state_transition_source', verbose_name=_(u'workflow state source')) + transition = models.ForeignKey(Transition, related_name='workflow_state_transition', verbose_name=_(u'transition')) + workflow_state_destination = models.ForeignKey(WorkflowState, related_name='workflow_state_transition_destination', verbose_name=_(u'workflow state destination')) + description = models.TextField(blank=True, verbose_name=_(u'description')) + + def __unicode__(self): + return unicode(self.transition) + + class Meta: + verbose_name = _(u'workflow state transition') + verbose_name_plural = _(u'workflows states transitions') + + +class WorkflowStateTransitionAbility(models.Model): + attribute_comparison_operand = models.CharField(max_length=8, default=OPERAND_AND, choices=OPERAND_CHOICES, verbose_name=_(u'operand')) + negate = models.BooleanField(verbose_name=_(u'negate'), help_text=_(u'Inverts the attribute comparison.')) + ability = models.ForeignKey(Ability, related_name='workflow_state_transition_ability', verbose_name=_(u'ability')) + + description = models.TextField(blank=True, verbose_name=_(u'description')) + + def __unicode__(self): + return unicode(self.ability) + + class Meta: + verbose_name = _(u'transition') + verbose_name_plural = _(u'transitions') + + +class WorkflowInstance(models.Model): + workflow = models.ForeignKey(Workflow, verbose_name = _(u'workflow')) + content_type = models.ForeignKey(ContentType, verbose_name=_(u'Content type'), related_name='workflow_instance_object')#, blank=True, null=True) + object_id = models.PositiveIntegerField()#blank=True, null=True) + content_object = generic.GenericForeignKey(ct_field='content_type', fk_field='object_id') + workflow_state = models.ForeignKey(WorkflowState, related_name='workflow_instance_state', verbose_name=_(u'state')) + + def __unicode__(self): + return unicode(self.content_object) + + class Meta: + unique_together = ('content_type', 'object_id', 'workflow') diff --git a/apps/workflows/permissions.py b/apps/workflows/permissions.py new file mode 100644 index 0000000000..ef1939942c --- /dev/null +++ b/apps/workflows/permissions.py @@ -0,0 +1,9 @@ +from __future__ import absolute_import + +from django.utils.translation import ugettext_lazy as _ + +from permissions.models import PermissionNamespace, Permission + +user_management_namespace = PermissionNamespace('workflows', _(u'Workflows')) + +PERMISSION_WORKFLOW_SETUP_VIEW = Permission.objects.register(user_management_namespace, 'workflow_setup_view', _(u'View existing workflows templates')) diff --git a/apps/workflows/static/images/icons/chart_organisation.png b/apps/workflows/static/images/icons/chart_organisation.png new file mode 100644 index 0000000000..5d408a347e Binary files /dev/null and b/apps/workflows/static/images/icons/chart_organisation.png differ diff --git a/apps/workflows/static/images/icons/chart_organisation_delete.png b/apps/workflows/static/images/icons/chart_organisation_delete.png new file mode 100644 index 0000000000..78cc3b9d17 Binary files /dev/null and b/apps/workflows/static/images/icons/chart_organisation_delete.png differ diff --git a/apps/workflows/urls.py b/apps/workflows/urls.py new file mode 100644 index 0000000000..bcad909101 --- /dev/null +++ b/apps/workflows/urls.py @@ -0,0 +1,5 @@ +from django.conf.urls.defaults import patterns, url + +urlpatterns = patterns('workflows.views', + url(r'^setup/list/$', 'setup_workflow_list', (), 'setup_workflow_list'), +) diff --git a/apps/workflows/views.py b/apps/workflows/views.py index 60f00ef0ef..ae8d56dbb6 100644 --- a/apps/workflows/views.py +++ b/apps/workflows/views.py @@ -1 +1,57 @@ -# Create your views here. +from __future__ import absolute_import + +import logging + +from django.http import HttpResponseRedirect +from django.shortcuts import render_to_response, get_object_or_404 +from django.template import RequestContext +from django.contrib import messages +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext +from django.utils.safestring import mark_safe +from django.conf import settings +from django.core.exceptions import PermissionDenied + +#from documents.permissions import (PERMISSION_DOCUMENT_CREATE, +# PERMISSION_DOCUMENT_NEW_VERSION) +#from documents.models import DocumentType, Document +#from documents.conf.settings import THUMBNAIL_SIZE +#from metadata.api import decode_metadata_from_url, metadata_repr_as_list +from permissions.models import Permission +#from common.utils import encapsulate +#from common.widgets import two_state_template +#import sendfile +#from acls.models import AccessEntry + +from .models import Workflow +#from .literals import (SOURCE_CHOICE_WEB_FORM, SOURCE_CHOICE_STAGING, +# SOURCE_CHOICE_WATCH, SOURCE_CHOICE_POP3_EMAIL, SOURCE_CHOICE_IMAP_EMAIL) +#from .literals import (SOURCE_UNCOMPRESS_CHOICE_Y, +# SOURCE_UNCOMPRESS_CHOICE_ASK) +#from .staging import create_staging_file_class +#from .forms import (StagingDocumentForm, WebFormForm, +# WatchFolderSetupForm) +from .permissions import PERMISSION_WORKFLOW_SETUP_VIEW + + +logger = logging.getLogger(__name__) + + +# Setup views +def setup_workflow_list(request): + Permission.objects.check_permissions(request.user, [PERMISSION_WORKFLOW_SETUP_VIEW]) + + context = { + 'object_list': Workflow.objects.all(), + 'title': _(u'workflows'), + #'hide_link': True, + #'list_object_variable_name': 'source', + #'source_type': source_type, + #'extra_columns': [ + # {'name': _(u'Enabled'), 'attribute': encapsulate(lambda source: two_state_template(source.enabled))}, + #], + } + + return render_to_response('generic_list.html', context, + context_instance=RequestContext(request))