diff --git a/apps/workflows/__init__.py b/apps/workflows/__init__.py index 555950daeb..d4a714a21e 100644 --- a/apps/workflows/__init__.py +++ b/apps/workflows/__init__.py @@ -9,11 +9,9 @@ from .permissions import (PERMISSION_WORKFLOW_SETUP_VIEW, PERMISSION_WORKFLOW_SETUP_CREATE, PERMISSION_WORKFLOW_SETUP_EDIT, PERMISSION_WORKFLOW_SETUP_DELETE, PERMISSION_STATE_SETUP_VIEW, PERMISSION_STATE_SETUP_CREATE, PERMISSION_STATE_SETUP_EDIT, - PERMISSION_STATE_SETUP_DELETE, PERMISSION_TRANSITION_SETUP_VIEW, - PERMISSION_TRANSITION_SETUP_CREATE, PERMISSION_TRANSITION_SETUP_EDIT, - PERMISSION_TRANSITION_SETUP_DELETE) -from .models import (Workflow, State, Transition, WorkflowState, - WorkflowStateTransition) + PERMISSION_STATE_SETUP_DELETE) + +from .models import (Workflow, State, WorkflowState, WorkflowNode) setup_workflow_list_link = Link(text=_(u'workflow list'), view='setup_workflow_list', sprite='chart_organisation', permissions=[PERMISSION_WORKFLOW_SETUP_VIEW]) setup_workflow_create_link = Link(text=_(u'create new workflow'), view='setup_workflow_create', sprite='chart_organisation_add', permissions=[PERMISSION_WORKFLOW_SETUP_CREATE]) @@ -33,34 +31,38 @@ setup_state_create_link = Link(text=_(u'create new state'), view='setup_state_cr setup_state_edit_link = Link(text=_(u'edit'), view='setup_state_edit', args='object.pk', sprite='transmit_edit', permissions=[PERMISSION_STATE_SETUP_EDIT]) setup_state_delete_link = Link(text=_(u'delete'), view='setup_state_delete', args='object.pk', sprite='transmit_delete', permissions=[PERMISSION_STATE_SETUP_DELETE]) -setup_transition_list_link = Link(text=_(u'transition list'), view='setup_transition_list', sprite='chart_line', permissions=[PERMISSION_TRANSITION_SETUP_VIEW]) -setup_transition_create_link = Link(text=_(u'create new transition'), view='setup_transition_create', sprite='chart_line_add', permissions=[PERMISSION_TRANSITION_SETUP_CREATE]) -setup_transition_edit_link = Link(text=_(u'edit'), view='setup_transition_edit', args='transition.pk', sprite='chart_line', permissions=[PERMISSION_TRANSITION_SETUP_EDIT]) -setup_transition_delete_link = Link(text=_(u'delete'), view='setup_transition_delete', args='transition.pk', sprite='chart_line_delete', permissions=[PERMISSION_TRANSITION_SETUP_DELETE]) +setup_workflow_node_list_link = Link(text=_(u'node list'), view='setup_workflow_node_list', args='workflow.pk', sprite='chart_line', permissions=[PERMISSION_WORKFLOW_SETUP_EDIT]) +#setup_workflow_node_create_link = Link(text=_(u'create new transition'), view='setup_transition_create', sprite='chart_line_add', permissions=[PERMISSION_TRANSITION_SETUP_CREATE]) +setup_workflow_node_edit_link = Link(text=_(u'edit'), view='setup_workflow_node_edit', args='workflow_node.pk', sprite='chart_line_edit', permissions=[PERMISSION_WORKFLOW_SETUP_EDIT]) +#setup_workflow_node_delete_link = Link(text=_(u'delete'), view='setup_transition_delete', args='transition.pk', sprite='chart_line_delete', permissions=[PERMISSION_TRANSITION_SETUP_DELETE]) bind_links( [ - Workflow, State, Transition, WorkflowStateTransition, + Workflow, State, 'setup_workflow_list', 'setup_workflow_create', 'setup_state_list', 'setup_state_create', - 'setup_transition_list', 'setup_transition_create', + #'setup_transition_list', 'setup_transition_create', + 'setup_transition_create', ], [ - setup_workflow_list_link, setup_state_list_link, setup_transition_list_link + setup_workflow_list_link, setup_state_list_link#, setup_transition_list_link ], menu_name=u'form_header') -bind_links([Workflow], [setup_workflow_states_list_link, setup_workflow_edit_link, setup_workflow_delete_link]) +bind_links([Workflow], [setup_workflow_node_list_link, setup_workflow_states_list_link, setup_workflow_edit_link, setup_workflow_delete_link]) bind_links([Workflow, 'setup_workflow_list', 'setup_workflow_create'], [setup_workflow_create_link], menu_name=u'secondary_menu') bind_links([WorkflowState, 'setup_workflow_states_list', 'setup_workflow_states_add'], [setup_workflow_states_add_link], menu_name=u'sidebar') bind_links([State], [setup_state_edit_link, setup_state_delete_link]) bind_links([State, 'setup_state_list', 'setup_state_create'], [setup_state_create_link], menu_name=u'secondary_menu') -bind_links([Transition], [setup_transition_edit_link, setup_transition_delete_link]) -bind_links([Transition, 'setup_transition_list', 'setup_transition_create'], [setup_transition_create_link], menu_name=u'secondary_menu') +#bind_links([Transition], [setup_transition_edit_link, setup_transition_delete_link]) +#bind_links([Transition, 'setup_transition_list', 'setup_transition_create'], [setup_transition_create_link], menu_name=u'secondary_menu') -bind_links([WorkflowState], [setup_workflow_state_transitions_list_link, setup_workflow_states_edit_link, setup_workflow_states_remove_link]) -bind_links([WorkflowState], [setup_workflow_state_transition_add_link], menu_name=u'sidebar') +#bind_links([WorkflowState], [setup_workflow_state_transitions_list_link, setup_workflow_states_edit_link, setup_workflow_states_remove_link]) +bind_links([WorkflowState], [setup_workflow_states_edit_link, setup_workflow_states_remove_link]) -bind_links([WorkflowStateTransition], [setup_workflow_state_transition_edit_link])#, setup_transition_delete_link]) +#bind_links([WorkflowState], [setup_workflow_state_transition_add_link], menu_name=u'sidebar') +#bind_links([WorkflowNode], [setup_workflow_state_transition_add_link], menu_name=u'sidebar') + +bind_links([WorkflowNode], [setup_workflow_node_edit_link])#, setup_transition_delete_link]) register_setup(Link(text=_(u'workflows'), view='setup_workflow_list', icon='chart_organisation.png', permissions=[PERMISSION_WORKFLOW_SETUP_VIEW])) diff --git a/apps/workflows/forms.py b/apps/workflows/forms.py index 5179d9a3d6..5f0e897ebd 100644 --- a/apps/workflows/forms.py +++ b/apps/workflows/forms.py @@ -3,11 +3,26 @@ from __future__ import absolute_import from django import forms from django.utils.translation import ugettext_lazy as _ -from .models import Workflow, State, Transition, WorkflowState, WorkflowStateTransition +from .models import Workflow, State, WorkflowState, WorkflowNode +class NodeForm(forms.Form): + def __init__(self, *args, **kwargs): + #workflow = kwargs.pop('workflow') + super(WorkflowStateSetupForm, self).__init__(*args, **kwargs) + #self.fields['workflow'].initial = workflow + #self.fields['workflow'].widget = forms.widgets.HiddenInput() + print self.instance + + #def choices(self, workflow): + # return { + ## 'next_node': workflow.nodes.all() + # } + + class WorkflowSetupForm(forms.ModelForm): class Meta: + exclude = ('initial_node,') model = Workflow @@ -27,19 +42,29 @@ class WorkflowStateSetupForm(forms.ModelForm): model = WorkflowState -class TransitionSetupForm(forms.ModelForm): - class Meta: - model = Transition - - -class WorkflowStateTransitionSetupForm(forms.ModelForm): +class WorkflowNodeSetupForm(forms.ModelForm): def __init__(self, *args, **kwargs): - workflow_state = kwargs.pop('workflow_state') - super(WorkflowStateTransitionSetupForm, self).__init__(*args, **kwargs) - self.fields['workflow_state_source'].initial = workflow_state - self.fields['workflow_state_source'].widget = forms.widgets.HiddenInput() + workflow = kwargs.pop('workflow') + super(WorkflowNodeSetupForm, self).__init__(*args, **kwargs) + self.fields['workflow'].initial = workflow + self.fields['workflow'].widget = forms.widgets.HiddenInput() class Meta: - model = WorkflowStateTransition + model = WorkflowNode + +#class TransitionSetupForm(forms.ModelForm): +# class Meta: +# model = Transition + + +#class WorkflowStateTransitionSetupForm(forms.ModelForm): +# def __init__(self, *args, **kwargs): +# workflow_state = kwargs.pop('workflow_state') +# super(WorkflowStateTransitionSetupForm, self).__init__(*args, **kwargs) +# self.fields['workflow_state_source'].initial = workflow_state +# self.fields['workflow_state_source'].widget = forms.widgets.HiddenInput() +# +# class Meta: +# model = WorkflowStateTransition diff --git a/apps/workflows/literals.py b/apps/workflows/literals.py index 8811669442..da3e4aeb23 100644 --- a/apps/workflows/literals.py +++ b/apps/workflows/literals.py @@ -7,3 +7,13 @@ OPERAND_CHOICES = ( (OPERAND_OR, _(u'or')), (OPERAND_AND, _(u'and')) ) + +NODE_TYPE_TASK = 'task' +NODE_TYPE_START = 'start' +NODE_TYPE_END = 'end' + +NODE_TYPE_CHOICES = ( + (NODE_TYPE_TASK, _(u'Simple task')), + (NODE_TYPE_START, _(u'Start')), + (NODE_TYPE_END, _(u'End')), +) diff --git a/apps/workflows/migrations/0002_auto__del_workflowstateabilitygrant__del_workflowstatetransition__del_.py b/apps/workflows/migrations/0002_auto__del_workflowstateabilitygrant__del_workflowstatetransition__del_.py new file mode 100644 index 0000000000..6c8fb3eac5 --- /dev/null +++ b/apps/workflows/migrations/0002_auto__del_workflowstateabilitygrant__del_workflowstatetransition__del_.py @@ -0,0 +1,231 @@ +# 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): + + # Removing unique constraint on 'WorkflowInstance', fields ['object_id', 'content_type', 'workflow'] + db.delete_unique('workflows_workflowinstance', ['object_id', 'content_type_id', 'workflow_id']) + + # 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 'Transition' + db.delete_table('workflows_transition') + + # Adding model 'WorkflowInstanceActiveState' + db.create_table('workflows_workflowinstanceactivestate', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('workflow_instance', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['workflows.WorkflowInstance'])), + ('state', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['workflows.State'], null=True)), + )) + db.send_create_signal('workflows', ['WorkflowInstanceActiveState']) + + # Adding unique constraint on 'WorkflowInstanceActiveState', fields ['workflow_instance', 'state'] + db.create_unique('workflows_workflowinstanceactivestate', ['workflow_instance_id', 'state_id']) + + # Adding model 'End' + db.create_table('workflows_end', ( + ('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', ['End']) + + # Adding model 'WorkflowInstanceActiveNode' + db.create_table('workflows_workflowinstanceactivenode', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('workflow_instance', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['workflows.WorkflowInstance'])), + ('workflow_node', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['workflows.WorkflowNode'])), + )) + db.send_create_signal('workflows', ['WorkflowInstanceActiveNode']) + + # Adding model 'Start' + db.create_table('workflows_start', ( + ('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)), + ('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'], null=True)), + ('object_id', self.gf('django.db.models.fields.PositiveIntegerField')(null=True)), + )) + db.send_create_signal('workflows', ['Start']) + + # Adding model 'WorkflowNode' + db.create_table('workflows_workflownode', ( + ('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')(to=orm['contenttypes.ContentType'])), + ('object_id', self.gf('django.db.models.fields.PositiveIntegerField')()), + ('description', self.gf('django.db.models.fields.TextField')(blank=True)), + )) + db.send_create_signal('workflows', ['WorkflowNode']) + + # Adding field 'Workflow.initial_node' + db.add_column('workflows_workflow', 'initial_node', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='workflow_initial_node', null=True, to=orm['workflows.WorkflowNode']), keep_default=False) + + # Deleting field 'WorkflowInstance.workflow_state' + db.delete_column('workflows_workflowinstance', 'workflow_state_id') + + # Adding unique constraint on 'WorkflowState', fields ['state', 'workflow'] + db.create_unique('workflows_workflowstate', ['state_id', 'workflow_id']) + + + def backwards(self, orm): + + # Removing unique constraint on 'WorkflowState', fields ['state', 'workflow'] + db.delete_unique('workflows_workflowstate', ['state_id', 'workflow_id']) + + # Removing unique constraint on 'WorkflowInstanceActiveState', fields ['workflow_instance', 'state'] + db.delete_unique('workflows_workflowinstanceactivestate', ['workflow_instance_id', 'state_id']) + + # Adding model 'WorkflowStateAbilityGrant' + db.create_table('workflows_workflowstateabilitygrant', ( + ('workflow_state', self.gf('django.db.models.fields.related.ForeignKey')(related_name='workflow_state_ability', to=orm['workflows.WorkflowState'])), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('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', ( + ('description', self.gf('django.db.models.fields.TextField')(blank=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'])), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + )) + db.send_create_signal('workflows', ['WorkflowStateTransition']) + + # Adding model 'WorkflowStateTransitionAbility' + db.create_table('workflows_workflowstatetransitionability', ( + ('ability', self.gf('django.db.models.fields.related.ForeignKey')(related_name='workflow_state_transition_ability', to=orm['workflows.Ability'])), + ('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)), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('description', self.gf('django.db.models.fields.TextField')(blank=True)), + )) + db.send_create_signal('workflows', ['WorkflowStateTransitionAbility']) + + # Adding model 'Transition' + db.create_table('workflows_transition', ( + ('label', self.gf('django.db.models.fields.CharField')(max_length=128)), + ('description', self.gf('django.db.models.fields.TextField')(blank=True)), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + )) + db.send_create_signal('workflows', ['Transition']) + + # Deleting model 'WorkflowInstanceActiveState' + db.delete_table('workflows_workflowinstanceactivestate') + + # Deleting model 'End' + db.delete_table('workflows_end') + + # Deleting model 'WorkflowInstanceActiveNode' + db.delete_table('workflows_workflowinstanceactivenode') + + # Deleting model 'Start' + db.delete_table('workflows_start') + + # Deleting model 'WorkflowNode' + db.delete_table('workflows_workflownode') + + # Deleting field 'Workflow.initial_node' + db.delete_column('workflows_workflow', 'initial_node_id') + + # User chose to not deal with backwards NULL issues for 'WorkflowInstance.workflow_state' + raise RuntimeError("Cannot reverse this migration. 'WorkflowInstance.workflow_state' and its values cannot be restored.") + + # Adding unique constraint on 'WorkflowInstance', fields ['object_id', 'content_type', 'workflow'] + db.create_unique('workflows_workflowinstance', ['object_id', 'content_type_id', 'workflow_id']) + + + 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.end': { + 'Meta': {'object_name': 'End'}, + '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.start': { + 'Meta': {'object_name': 'Start'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True'}), + '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'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}) + }, + '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.workflow': { + 'Meta': {'object_name': 'Workflow'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'initial_node': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_initial_node'", 'null': 'True', 'to': "orm['workflows.WorkflowNode']"}), + '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': {'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']"}) + }, + 'workflows.workflowinstanceactivenode': { + 'Meta': {'object_name': 'WorkflowInstanceActiveNode'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'workflow_instance': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.WorkflowInstance']"}), + 'workflow_node': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.WorkflowNode']"}) + }, + 'workflows.workflowinstanceactivestate': { + 'Meta': {'unique_together': "(('workflow_instance', 'state'),)", 'object_name': 'WorkflowInstanceActiveState'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.State']", 'null': 'True'}), + 'workflow_instance': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.WorkflowInstance']"}) + }, + 'workflows.workflownode': { + 'Meta': {'object_name': 'WorkflowNode'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + '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']"}) + }, + 'workflows.workflowstate': { + 'Meta': {'unique_together': "(('workflow', 'state'),)", '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', [], {'to': "orm['workflows.State']"}), + 'workflow': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.Workflow']"}) + } + } + + complete_apps = ['workflows'] diff --git a/apps/workflows/models.py b/apps/workflows/models.py index 55a3958402..a927d291be 100644 --- a/apps/workflows/models.py +++ b/apps/workflows/models.py @@ -4,11 +4,14 @@ 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 django.utils.translation import ugettext from permissions.models import StoredPermission from .literals import OPERAND_CHOICES, OPERAND_AND +#from .literals import NODE_TYPE_TASK, NODE_TYPE_START, NODE_TYPE_END +#NODE_TYPE_CHOICES class Ability(models.Model): label = models.CharField(max_length=128, unique=True, verbose_name = _(u'label')) @@ -24,44 +27,149 @@ class Ability(models.Model): class Workflow(models.Model): label = models.CharField(max_length=128, unique=True, verbose_name = _(u'label')) + initial_node = models.ForeignKey('WorkflowNode', related_name='workflow_initial_node', blank=True, null=True, verbose_name=_(u'initial node')) 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 + @property + def workflow_nodes(self): + return self.workflownode_set + + def get_nodes(self): + return [workflow_node.node for workflow_node in self.workflownode_set.all()] + + def add_node(self, node): + workflow_node = WorkflowNode( + workflow=self, + node=node) + workflow_node.save() + return workflow_node + + def save(self, *args, **kwargs): + is_new = not self.pk + result = super(Workflow, self).save(*args, **kwargs) + + if is_new: + # Instanciate a new start node + start_node = Start() + start_node.save() + + # Set the start node a + workflow_node = self.add_node(node=start_node) + + self.initial_node = workflow_node + self.save() + + return result + class Meta: verbose_name = _(u'workflow') verbose_name_plural = _(u'workflows') - - -class State(models.Model): + + +class Node(models.Model): + """ + Must provide: + possible_next_nodes() + Arguments: None + Returns: + List of possible nodes after this one executes + + choices() + Arguments: workflow + Returns: + { + 'next_node': workflow.nodes.all() + } + + execute() + Arguments: workflow_instance + Returns: next_node + """ + 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') + abstract = True + + +class Start(Node): + """ + The node with which all workflows start + """ + content_type = models.ForeignKey(ContentType, null=True) + object_id = models.PositiveIntegerField(null=True) + next_node = generic.GenericForeignKey(ct_field='content_type', fk_field='object_id') + + def __unicode__(self): + return ugettext(u'Start') + + def possible_next_nodes(self): + return self.next_node + + def choices(self, workflow): + return { + 'next_node': workflow.nodes.all() + } + + def execute(self, workflow_instance): + return self.next_node + + class Meta(Node.Meta): + verbose_name = _(u'start') + verbose_name_plural = _(u'starts') -class Transition(models.Model): +class End(Node): + class Meta(Node.Meta): + verbose_name = _(u'start') + verbose_name_plural = _(u'starts') + + +''' +class Sequence(Node): + """ + A node that is enabled after the completion of a preceding node in the same workflow + """ + content_type = models.ForeignKey(ContentType) + object_id = models.PositiveIntegerField() + next_node = generic.GenericForeignKey(ct_field='content_type', fk_field='object_id') + #node_type = NODE_TYPE_TASK + + def execute(self): + return self.next_node + + class Meta(Node.Meta): + verbose_name = _(u'task') + verbose_name_plural = _(u'tasks') +''' + +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'transition') - verbose_name_plural = _(u'transitions') - - + verbose_name = _(u'state') + verbose_name_plural = _(u'states') + ordering = ('label',) + + class WorkflowState(models.Model): + """ + List of possible states the object of this worflow could be in + """ workflow = models.ForeignKey(Workflow, verbose_name=_(u'workflow')) state = models.ForeignKey(State, verbose_name=_(u'state')) description = models.TextField(blank=True, verbose_name=_(u'description')) @@ -69,17 +177,33 @@ class WorkflowState(models.Model): def __unicode__(self): return unicode(self.state) - @property - def transitions(self): - return self.workflowstatetransition_set - class Meta: - #unique_together = ('workflow', 'state') + unique_together = ('workflow', 'state') verbose_name = _(u'workflow state') verbose_name_plural = _(u'workflows states') +# TODO: Reduntant - remove +class WorkflowNode(models.Model): + workflow = models.ForeignKey(Workflow, verbose_name=_(u'workflow')) + content_type = models.ForeignKey(ContentType)#, limit_choices_to={'model__in': ('model1', 'model2')})#, related_name='workflow_state_ability_object')#, blank=True, null=True) + object_id = models.PositiveIntegerField()#blank=True, null=True) + node = generic.GenericForeignKey(ct_field='content_type', fk_field='object_id') + description = models.TextField(blank=True, verbose_name=_(u'description')) + + def __unicode__(self): + return unicode(self.node) -class WorkflowStateAbilityGrant(models.Model): + #def save(self, *args, **kwargs): + # if not issubclass( + # return super(WorkflowNode, self).save(*args, **kwargs) + + class Meta: + #unique_together = ('workflow', 'state') + verbose_name = _(u'workflow node') + verbose_name_plural = _(u'workflows nodes') + +""" +class WorkflowTaskAbilityGrant(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) @@ -133,16 +257,69 @@ class WorkflowStateTransitionAbility(models.Model): verbose_name = _(u'transition') verbose_name_plural = _(u'transitions') - +""" class WorkflowInstance(models.Model): - workflow = models.ForeignKey(Workflow, verbose_name = _(u'workflow')) + 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) + + @property + def active_nodes(self): + return self.workflowinstanceactivenode_set + + def set_active_state(self, state): + active_state = self.get_active_state() + if active_state: + active_state.delete() + + # Trigger an exception if the state argument if not allowed for this workflow + state = WorkflowState.objects.get(workflow=self.workflow, state=state).state + + self.workflowinstanceactivestate_set.create( + workflow_instance = self, + state = state + ) + + def get_active_state(self): + try: + return self.workflowinstanceactivestate_set.get().state + except WorkflowInstanceActiveState.DoesNotExist: + return None + + active_state = property(get_active_state, set_active_state) class Meta: - unique_together = ('content_type', 'object_id', 'workflow') + verbose_name = _(u'workflow instance') + verbose_name_plural = _(u'workflow instances') + + # unique_together = ('content_type', 'object_id', 'workflow') + + +class WorkflowInstanceActiveNode(models.Model): + workflow_instance = models.ForeignKey(WorkflowInstance, verbose_name=_(u'workflow instance')) + workflow_node = models.ForeignKey(WorkflowNode, verbose_name=_(u'workflow node')) + + class Meta: + verbose_name = _(u'workflow instance active node') + verbose_name_plural = _(u'workflow instances active nodes') + + +class WorkflowInstanceActiveState(models.Model): + """ + This class holds the active state for the workflow instance + """ + workflow_instance = models.ForeignKey(WorkflowInstance, verbose_name=_(u'workflow instance')) + state = models.ForeignKey(State, null=True, verbose_name=_(u'state')) + + class Meta: + unique_together = ('workflow_instance', 'state') + verbose_name = _(u'workflow instance active state') + verbose_name_plural = _(u'workflow instances active states') + + +# TODO: WorkflowInstanceActiveNodeHistory +# TODO: WorkflowInstanceActiveStateHistory diff --git a/apps/workflows/permissions.py b/apps/workflows/permissions.py index 5f27a47854..be443bda66 100644 --- a/apps/workflows/permissions.py +++ b/apps/workflows/permissions.py @@ -16,8 +16,8 @@ PERMISSION_STATE_SETUP_CREATE = Permission.objects.register(namespace, 'state_se PERMISSION_STATE_SETUP_EDIT = Permission.objects.register(namespace, 'state_setup_edit', _(u'Edit existing state templates')) PERMISSION_STATE_SETUP_DELETE = Permission.objects.register(namespace, 'state_setup_delete', _(u'Delete existing state templates')) -PERMISSION_TRANSITION_SETUP_VIEW = Permission.objects.register(namespace, 'transition_setup_view', _(u'View existing transition templates')) -PERMISSION_TRANSITION_SETUP_CREATE = Permission.objects.register(namespace, 'transition_setup_create', _(u'Create new transition templates')) -PERMISSION_TRANSITION_SETUP_EDIT = Permission.objects.register(namespace, 'transition_setup_edit', _(u'Edit existing transition templates')) -PERMISSION_TRANSITION_SETUP_DELETE = Permission.objects.register(namespace, 'transition_setup_delete', _(u'Delete existing transition templates')) +#PERMISSION_TRANSITION_SETUP_VIEW = Permission.objects.register(namespace, 'transition_setup_view', _(u'View existing transition templates')) +#PERMISSION_TRANSITION_SETUP_CREATE = Permission.objects.register(namespace, 'transition_setup_create', _(u'Create new transition templates')) +#PERMISSION_TRANSITION_SETUP_EDIT = Permission.objects.register(namespace, 'transition_setup_edit', _(u'Edit existing transition templates')) +#PERMISSION_TRANSITION_SETUP_DELETE = Permission.objects.register(namespace, 'transition_setup_delete', _(u'Delete existing transition templates')) diff --git a/apps/workflows/urls.py b/apps/workflows/urls.py index 19a9dbcfd3..29b54ab972 100644 --- a/apps/workflows/urls.py +++ b/apps/workflows/urls.py @@ -11,17 +11,11 @@ urlpatterns = patterns('workflows.views', url(r'^setup/workflow/state/(?P\d+)/edit/$', 'setup_workflow_state_edit', (), 'setup_workflow_state_edit'), url(r'^setup/workflow/state/(?P\d+)/remove/$', 'setup_workflow_state_remove', (), 'setup_workflow_state_remove'), - url(r'^setup/workflow/state/(?P\d+)/transition/list/$', 'setup_workflow_state_transitions_list', (), 'setup_workflow_state_transitions_list'), - url(r'^setup/workflow/state/(?P\d+)/transition/add/$', 'setup_workflow_state_transition_add', (), 'setup_workflow_state_transition_add'), - url(r'^setup/workflow/state/transition/(?P\d+)/edit/$', 'setup_workflow_state_transition_edit', (), 'setup_workflow_state_transition_edit'), - url(r'^setup/state/list/$', 'setup_state_list', (), 'setup_state_list'), url(r'^setup/state/create/$', 'setup_state_create', (), 'setup_state_create'), url(r'^setup/state/(?P\d+)/edit/$', 'setup_state_edit', (), 'setup_state_edit'), url(r'^setup/state/(?P\d+)/delete/$', 'setup_state_delete', (), 'setup_state_delete'), - - url(r'^setup/transition/list/$', 'setup_transition_list', (), 'setup_transition_list'), - url(r'^setup/transition/create/$', 'setup_transition_create', (), 'setup_transition_create'), - url(r'^setup/transition/(?P\d+)/edit/$', 'setup_transition_edit', (), 'setup_transition_edit'), - url(r'^setup/transition/(?P\d+)/delete/$', 'setup_transition_delete', (), 'setup_transition_delete'), + + url(r'^setup/workflow/(?P\d+)/node/list/$', 'setup_workflow_node_list', (), 'setup_workflow_node_list'), + url(r'^setup/workflow/node/(?P\d+)/edit/$', 'setup_workflow_node_edit', (), 'setup_workflow_node_edit'), ) diff --git a/apps/workflows/views.py b/apps/workflows/views.py index 1373cfd8bc..33c5a778b9 100644 --- a/apps/workflows/views.py +++ b/apps/workflows/views.py @@ -18,18 +18,14 @@ from common.utils import encapsulate #from common.widgets import two_state_template #from acls.models import AccessEntry -from .models import Workflow, State, Transition, WorkflowState +from .models import Workflow, State, WorkflowState, WorkflowNode from .forms import (WorkflowSetupForm, StateSetupForm, - WorkflowStateSetupForm, TransitionSetupForm, - WorkflowStateTransitionSetupForm) + WorkflowStateSetupForm, WorkflowNodeSetupForm) from .permissions import (PERMISSION_WORKFLOW_SETUP_VIEW, PERMISSION_WORKFLOW_SETUP_CREATE, PERMISSION_WORKFLOW_SETUP_EDIT, PERMISSION_WORKFLOW_SETUP_DELETE, PERMISSION_STATE_SETUP_VIEW, PERMISSION_STATE_SETUP_CREATE, PERMISSION_STATE_SETUP_EDIT, - PERMISSION_STATE_SETUP_DELETE, PERMISSION_TRANSITION_SETUP_VIEW, - PERMISSION_TRANSITION_SETUP_CREATE, PERMISSION_TRANSITION_SETUP_EDIT, - PERMISSION_TRANSITION_SETUP_DELETE) - + PERMISSION_STATE_SETUP_DELETE) logger = logging.getLogger(__name__) @@ -207,7 +203,7 @@ def setup_workflow_state_edit(request, workflow_state_pk): form = WorkflowStateSetupForm(workflow=workflow_state.workflow, instance=workflow_state) return render_to_response('generic_form.html', { - 'title': _(u'edit worflow state'), + 'title': _(u'edit worflow state: %s') % workflow_state, 'form': form, 'workflow': workflow_state.workflow, 'workflow_state': workflow_state, @@ -272,6 +268,54 @@ def setup_workflow_state_remove(request, workflow_state_pk=None, workflow_state_ context_instance=RequestContext(request)) +# Nodes +def setup_workflow_node_list(request, workflow_pk): + Permission.objects.check_permissions(request.user, [PERMISSION_WORKFLOW_SETUP_EDIT]) + workflow = get_object_or_404(Workflow, pk=workflow_pk) + + context = { + 'object_list': workflow.workflow_nodes.all(), + 'extra_columns': [ + {'name': _(u'Posible next nodes'), 'attribute': encapsulate(lambda workflow_node: workflow_node.node.possible_next_nodes() or _(u'None'))}, + ], + 'title': _(u'nodes of workflow: %s') % workflow, + 'hide_link': True, + 'workflow': workflow, + 'navigation_object_list': [ + {'object': 'workflow', 'name': _(u'workflow')}, + ], + 'list_object_variable_name': 'workflow_node', + } + + return render_to_response('generic_list.html', context, + context_instance=RequestContext(request)) + + +def setup_workflow_node_edit(request, workflow_node_pk): + Permission.objects.check_permissions(request.user, [PERMISSION_WORKFLOW_SETUP_EDIT]) + workflow_node = get_object_or_404(WorkflowNode, pk=workflow_node_pk) + redirect_url = reverse('setup_workflow_node_list', args=[workflow_node.workflow.pk]) + + if request.method == 'POST': + form = WorkflowNodeSetupForm(workflow=workflow_node.workflow, instance=workflow_node, data=request.POST) + if form.is_valid(): + state = form.save() + messages.success(request, _(u'worflow node edited succesfully.')) + return HttpResponseRedirect(redirect_url) + else: + form = WorkflowNodeSetupForm(workflow=workflow_node.workflow, instance=workflow_node) + + return render_to_response('generic_form.html', { + 'title': _(u'edit worflow node: %s') % workflow_node, + 'form': form, + 'workflow': workflow_node.workflow, + 'workflow_node': workflow_node, + 'navigation_object_list': [ + {'object': 'workflow', 'name': _(u'workflow')}, + {'object': 'workflow_node', 'name': _(u'workflow node')} + ], + }, context_instance=RequestContext(request)) +""" def setup_workflow_transition_list(request, state_workflow_pk): Permission.objects.check_permissions(request.user, [PERMISSION_WORKFLOW_SETUP_EDIT]) workflow = get_object_or_404(Workflow, pk=workflow_pk) @@ -289,6 +333,7 @@ def setup_workflow_transition_list(request, state_workflow_pk): return render_to_response('generic_list.html', context, context_instance=RequestContext(request)) +""" # States def setup_state_list(request): Permission.objects.check_permissions(request.user, [PERMISSION_STATE_SETUP_VIEW]) @@ -395,7 +440,7 @@ def setup_state_delete(request, state_pk=None, state_pk_list=None): return render_to_response('generic_confirm.html', context, context_instance=RequestContext(request)) - +""" # Transitions def setup_transition_list(request): Permission.objects.check_permissions(request.user, [PERMISSION_TRANSITION_SETUP_VIEW]) @@ -507,8 +552,8 @@ def setup_transition_delete(request, transition_pk=None, transition_pk_list=None return render_to_response('generic_confirm.html', context, context_instance=RequestContext(request)) - - +""" +""" # State transitions def setup_workflow_state_transitions_list(request, workflow_state_pk): Permission.objects.check_permissions(request.user, [PERMISSION_WORKFLOW_SETUP_EDIT]) @@ -587,7 +632,7 @@ def setup_workflow_state_transition_edit(request, work_state_transition_pk): ], 'list_object_variable_name': 'workflow_state_transition', }, context_instance=RequestContext(request)) - +""" """ def setup_state_transition_remove(request, state_transition_pk=None, state_transition_pk_list=None): post_action_redirect = None