Skip to content

Commit

Permalink
[#110] Improve migration for more advance cases
Browse files Browse the repository at this point in the history
  • Loading branch information
javrasya committed Oct 27, 2019
1 parent 951ab76 commit 96e7d8e
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 9 deletions.
73 changes: 64 additions & 9 deletions river/migrations/0007_transitionapproval_iteration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
117 changes: 117 additions & 0 deletions river/tests/tmigrations/test__migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))))

0 comments on commit 96e7d8e

Please sign in to comment.