diff --git a/river/migrations/0007_transitionapproval_iteration.py b/river/migrations/0007_transitionapproval_iteration.py index 0630b0e..d2a8cf3 100644 --- a/river/migrations/0007_transitionapproval_iteration.py +++ b/river/migrations/0007_transitionapproval_iteration.py @@ -2,24 +2,79 @@ # Generated by Django 1.11.24 on 2019-10-25 21:43 from __future__ import unicode_literals +from datetime import timedelta +from operator import itemgetter + from django.db import migrations, models from django.db.migrations import RunPython +from river.models import APPROVED + + +def approval_key(approval): + return "%s-%s-%s" % (approval.priority, approval.source_state.pk, approval.destination_state.pk) + + +sql = """ +SELECT + ta.id, + ta.source_state_id, + ta.destination_state_id, + ta.transaction_date, + ta.status, + ( + select + count(*) + from river_transitionapproval ta_inner + where ta_inner.workflow_id=ta.workflow_id AND + ta_inner.object_id = ta.object_id AND + ta_inner.source_state_id=ta.source_state_id AND + ta_inner.destination_state_id=ta.destination_state_id AND + ta_inner.priority=ta.priority AND + ta_inner.date_created < ta.date_created + ) as rw_number +FROM river_transitionapproval ta +WHERE rw_number=%d AND ta.workflow_id='%s' and ta.object_id='%s'; +""" + def migrate_iteration(apps, schema_editor): + def _iterate(workflow, initial_state, workflow_object, last_approved_iteration=-1, generation=0): + with schema_editor.connection.cursor() as cursor: + output = cursor.execute(sql % (generation, workflow.pk, workflow_obj.pk)).fetchall() + if output: + output = [ + { + "pk": row[0], + "source_state_pk": row[1], + "destination_state_pk": row[2], + "approved_at": row[3], + "status": row[4] + } + for row in output + ] + + last_approved = None + approvals = list([approval for approval in output if approval["source_state_pk"] == initial_state.pk]) + iteration = last_approved_iteration + 1 + processed_source_states = set() + while approvals: + workflow.transition_approvals.filter(pk__in=[approval['pk'] for approval in approvals]).update(iteration=iteration) + destination_state_pks = [approval['destination_state_pk'] for approval in approvals] + last_approved = next(reversed(sorted(filter(lambda a: a['status'] == APPROVED, approvals), key=itemgetter("approved_at"))), None) + processed_source_states.update([approval['source_state_pk'] for approval in approvals]) + approvals = list([approval for approval in output if approval["source_state_pk"] in destination_state_pks and approval["source_state_pk"] not in processed_source_states]) + iteration += 1 + + if last_approved: + last_approved_object = workflow.transition_approvals.get(pk=last_approved["pk"]) + _iterate(workflow, last_approved_object.destination_state, workflow_object, last_approved_object.iteration, generation + 1) + Workflow = apps.get_model('river', 'Workflow') for workflow in Workflow.objects.all(): model_class = apps.get_model(workflow.content_type.app_label, workflow.content_type.model) for workflow_obj in model_class.objects.all(): - approvals = workflow.transition_approvals.filter(content_type=workflow.content_type, object_id=workflow_obj.pk) - iteration = 0 - current_source_state = workflow.initial_state - for approval in approvals: - if approval.source_state != current_source_state: - current_source_state = approval.source_state - iteration += 1 - approval.iteration = iteration - approval.save() + _iterate(workflow, workflow.initial_state, workflow_obj) class Migration(migrations.Migration): diff --git a/river/tests/tmigrations/test__migrations.py b/river/tests/tmigrations/test__migrations.py index 63c84ce..81f9736 100644 --- a/river/tests/tmigrations/test__migrations.py +++ b/river/tests/tmigrations/test__migrations.py @@ -347,3 +347,120 @@ def test__shouldMigrationForIterationMustFinishInShortAmountOfTimeWithTooManyObj call_command('migrate', 'river', '0007', stdout=out) after = datetime.now() assert_that(after - before, less_than(timedelta(seconds=60))) + + @skipUnless(django.VERSION[0] < 2, "Is not able to run with new version of django") + def test__shouldAssessIterationsForExistingApprovalsWhenThereIsMoreAdvanceCycle(self): + out = StringIO() + sys.stout = out + + authorized_permission1 = PermissionObjectFactory() + authorized_permission2 = PermissionObjectFactory() + authorized_user = UserObjectFactory(user_permissions=[authorized_permission1, authorized_permission2]) + + opn = StateObjectFactory(label="open") + in_progress = StateObjectFactory(label="in_progress") + resolved = StateObjectFactory(label="resolved") + re_opened = StateObjectFactory(label="re_opened") + closed = StateObjectFactory(label="closed") + final = StateObjectFactory(label="final") + + workflow = WorkflowFactory(initial_state=opn, content_type=ContentType.objects.get_for_model(BasicTestModel), field_name="my_field") + + open_to_in_progress = TransitionApprovalMetaFactory.create( + workflow=workflow, + source_state=opn, + destination_state=in_progress, + priority=0, + permissions=[authorized_permission1] + ) + + in_progress_to_resolved = TransitionApprovalMetaFactory.create( + workflow=workflow, + source_state=in_progress, + destination_state=resolved, + priority=0, + permissions=[authorized_permission1] + ) + + resolved_to_re_opened = TransitionApprovalMetaFactory.create( + workflow=workflow, + source_state=resolved, + destination_state=re_opened, + priority=0, + permissions=[authorized_permission2] + ) + + re_opened_to_in_progress = TransitionApprovalMetaFactory.create( + workflow=workflow, + source_state=re_opened, + destination_state=in_progress, + priority=0, + permissions=[authorized_permission1] + ) + + resolved_to_closed = TransitionApprovalMetaFactory.create( + workflow=workflow, + source_state=resolved, + destination_state=closed, + priority=0, + permissions=[authorized_permission1] + ) + + closed_to_final = TransitionApprovalMetaFactory.create( + workflow=workflow, + source_state=closed, + destination_state=final, + priority=0, + permissions=[authorized_permission1] + ) + + workflow_object = BasicTestModelObjectFactory() + + assert_that(workflow_object.model.my_field, equal_to(opn)) + workflow_object.model.river.my_field.approve(as_user=authorized_user) + assert_that(workflow_object.model.my_field, equal_to(in_progress)) + workflow_object.model.river.my_field.approve(as_user=authorized_user) + assert_that(workflow_object.model.my_field, equal_to(resolved)) + workflow_object.model.river.my_field.approve(as_user=authorized_user, next_state=re_opened) + assert_that(workflow_object.model.my_field, equal_to(re_opened)) + workflow_object.model.river.my_field.approve(as_user=authorized_user) + assert_that(workflow_object.model.my_field, equal_to(in_progress)) + + with connection.cursor() as cur: + result = cur.execute("select meta_id, iteration from river_transitionapproval where object_id=%s;" % workflow_object.model.pk).fetchall() + assert_that(result, has_length(11)) + assert_that(result, has_item(equal_to((open_to_in_progress.pk, 0)))) + assert_that(result, has_item(equal_to((in_progress_to_resolved.pk, 1)))) + assert_that(result, has_item(equal_to((resolved_to_closed.pk, 2)))) + assert_that(result, has_item(equal_to((resolved_to_re_opened.pk, 2)))) + assert_that(result, has_item(equal_to((re_opened_to_in_progress.pk, 3)))) + assert_that(result, has_item(equal_to((closed_to_final.pk, 3)))) + assert_that(result, has_item(equal_to((in_progress_to_resolved.pk, 4)))) + assert_that(result, has_item(equal_to((resolved_to_closed.pk, 5)))) + assert_that(result, has_item(equal_to((resolved_to_re_opened.pk, 5)))) + assert_that(result, has_item(equal_to((re_opened_to_in_progress.pk, 6)))) + assert_that(result, has_item(equal_to((closed_to_final.pk, 6)))) + + call_command('migrate', 'river', '0006', stdout=out) + + with connection.cursor() as cur: + schema = cur.execute("PRAGMA table_info('river_transitionapproval');").fetchall() + columns = six.moves.map(lambda column: column[1], schema) + assert_that(columns, is_not(has_item("iteration"))) + + call_command('migrate', 'river', '0007', stdout=out) + + with connection.cursor() as cur: + result = cur.execute("select meta_id, iteration from river_transitionapproval where object_id=%s;" % workflow_object.model.pk).fetchall() + assert_that(result, has_length(11)) + assert_that(result, has_item(equal_to((open_to_in_progress.pk, 0)))) + assert_that(result, has_item(equal_to((in_progress_to_resolved.pk, 1)))) + assert_that(result, has_item(equal_to((resolved_to_closed.pk, 2)))) + assert_that(result, has_item(equal_to((resolved_to_re_opened.pk, 2)))) + assert_that(result, has_item(equal_to((re_opened_to_in_progress.pk, 3)))) + assert_that(result, has_item(equal_to((closed_to_final.pk, 3)))) + assert_that(result, has_item(equal_to((in_progress_to_resolved.pk, 4)))) + assert_that(result, has_item(equal_to((resolved_to_closed.pk, 5)))) + assert_that(result, has_item(equal_to((resolved_to_re_opened.pk, 5)))) + assert_that(result, has_item(equal_to((re_opened_to_in_progress.pk, 6)))) + assert_that(result, has_item(equal_to((closed_to_final.pk, 6))))