diff --git a/ietf/doc/migrations/0011_reviewassignmentdocevent.py b/ietf/doc/migrations/0011_reviewassignmentdocevent.py new file mode 100644 index 000000000..1e2e40bfc --- /dev/null +++ b/ietf/doc/migrations/0011_reviewassignmentdocevent.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.18 on 2019-01-11 11:22 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import ietf.utils.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('review', '0009_refactor_review_request'), + ('name', '0006_adjust_statenames'), + ('doc', '0010_auto_20190225_1302'), + ] + + operations = [ + migrations.CreateModel( + name='ReviewAssignmentDocEvent', + fields=[ + ('docevent_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='doc.DocEvent')), + ('review_assignment', ietf.utils.models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='review.ReviewAssignment')), + ('state', ietf.utils.models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='name.ReviewAssignmentStateName')), + ], + bases=('doc.docevent',), + ), + ] diff --git a/ietf/name/migrations/0005_reviewassignmentstatename.py b/ietf/name/migrations/0005_reviewassignmentstatename.py new file mode 100644 index 000000000..dbf253e51 --- /dev/null +++ b/ietf/name/migrations/0005_reviewassignmentstatename.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.18 on 2019-01-04 13:59 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('name', '0004_add_prefix_to_doctypenames'), + ] + + operations = [ + migrations.CreateModel( + name='ReviewAssignmentStateName', + fields=[ + ('slug', models.CharField(max_length=32, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=255)), + ('desc', models.TextField(blank=True)), + ('used', models.BooleanField(default=True)), + ('order', models.IntegerField(default=0)), + ], + options={ + 'ordering': ['order', 'name'], + 'abstract': False, + }, + ), + ] diff --git a/ietf/name/migrations/0006_adjust_statenames.py b/ietf/name/migrations/0006_adjust_statenames.py new file mode 100644 index 000000000..52c094d34 --- /dev/null +++ b/ietf/name/migrations/0006_adjust_statenames.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.18 on 2019-01-04 14:02 +from __future__ import unicode_literals + +from django.db import migrations + +def forward(apps, schema_editor): + ReviewRequestStateName = apps.get_model('name','ReviewRequestStateName') + ReviewAssignmentStateName = apps.get_model('name','ReviewAssignmentStateName') + + # TODO: Remove these newly unused states in a future release + ReviewRequestStateName.objects.filter(slug__in=['accepted', 'rejected', 'no-response', 'part-completed', 'completed', 'unknown']).update(used=False) + ReviewRequestStateName.objects.create(slug = 'assigned', name = 'Assigned', desc = 'The ReviewRequest has been assigned to at least one reviewer' , used = True, order = 0) + + assignment_states = [ + { 'slug': 'assigned', + 'name': 'Assigned', + 'desc': 'The review has been assigned to this reviewer', + 'used': True, + 'order': 0 + }, + { 'slug':'accepted', + 'name':'Accepted', + 'desc':'The reviewer has accepted the assignment', + 'used': True, + 'order':0 + }, + { 'slug':'rejected', + 'name':'Rejected', + 'desc':'The reviewer has rejected the assignment', + 'used': True, + 'order':0 + }, + { 'slug':'withdrawn', + 'name':'Withdrawn by Team', + 'desc':'The team secretary has withdrawn the assignment', + 'used': True, + 'order':0 + }, + { 'slug':'overtaken', + 'name':'Overtaken By Events', + 'desc':'The review was abandoned because of circumstances', + 'used': True, + 'order':0 + }, + { 'slug':'no-response', + 'name':'No Response', + 'desc':'The reviewer did not provide a review by the deadline', + 'used': True, + 'order':0 + }, + { 'slug':'part-completed', + 'name':'Partially Completed', + 'desc':'The reviewer partially completed the assignment', + 'used': True, + 'order':0 + }, + { 'slug':'completed', + 'name':'Completed', + 'desc':'The reviewer completed the assignment', + 'used': True, + 'order':0 + }, + { 'slug':'unknown', + 'name':'Unknown', + 'desc':'The assignment is was imported from an earlier database and its state could not be computed', + 'used':'True', + 'order':0 + } + ] + + for entry in assignment_states: + ReviewAssignmentStateName.objects.create(**entry) + + +def reverse(apps, schema_editor): + ReviewRequestStateName = apps.get_model('name','ReviewRequestStateName') + ReviewAssignmentStateName = apps.get_model('name','ReviewAssignmentStateName') + ReviewRequestStateName.objects.filter(slug__in=['accepted', 'rejected', 'no-response', 'part-completed', 'completed', 'unknown']).update(used=True) + ReviewRequestStateName.objects.filter(slug='assigned').update(used=False) + ReviewAssignmentStateName.objects.update(used=False) + + +class Migration(migrations.Migration): + + dependencies = [ + ('name', '0005_reviewassignmentstatename'), + ] + + operations = [ + migrations.RunPython(forward, reverse) + ] diff --git a/ietf/review/migrations/0009_refactor_review_request.py b/ietf/review/migrations/0009_refactor_review_request.py new file mode 100644 index 000000000..64fc41c93 --- /dev/null +++ b/ietf/review/migrations/0009_refactor_review_request.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.18 on 2019-01-04 14:27 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import ietf.utils.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('doc', '0009_move_non_url_externalurls_to_uploaded_filename'), + ('name', '0006_adjust_statenames'), + ('person', '0008_auto_20181014_1448'), + ('review', '0008_remove_reviewrequest_old_id'), + ] + + operations = [ + migrations.CreateModel( + name='ReviewAssignment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('assigned_on', models.DateTimeField(blank=True, null=True)), + ('completed_on', models.DateTimeField(blank=True, null=True)), + ('reviewed_rev', models.CharField(blank=True, max_length=16, verbose_name=b'reviewed revision')), + ('mailarch_url', models.URLField(blank=True, null=True)), + ('result', ietf.utils.models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='name.ReviewResultName')), + ('review', ietf.utils.models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='doc.Document')), + ], + ), + migrations.RenameField( + model_name='reviewrequest', + old_name='result', + new_name='unused_result', + ), + migrations.RenameField( + model_name='reviewrequest', + old_name='review', + new_name='unused_review', + ), + migrations.RenameField( + model_name='reviewrequest', + old_name='reviewed_rev', + new_name='unused_reviewed_rev', + ), + migrations.RenameField( + model_name='reviewrequest', + old_name='reviewer', + new_name='unused_reviewer', + ), + migrations.AddField( + model_name='reviewassignment', + name='review_request', + field=ietf.utils.models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='review.ReviewRequest'), + ), + migrations.AddField( + model_name='reviewassignment', + name='reviewer', + field=ietf.utils.models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='person.Email'), + ), + migrations.AddField( + model_name='reviewassignment', + name='state', + field=ietf.utils.models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='name.ReviewAssignmentStateName'), + ), + ] diff --git a/ietf/review/migrations/0010_populate_review_assignments.py b/ietf/review/migrations/0010_populate_review_assignments.py new file mode 100644 index 000000000..f18b63d61 --- /dev/null +++ b/ietf/review/migrations/0010_populate_review_assignments.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.18 on 2019-01-04 14:34 +from __future__ import unicode_literals + +from tqdm import tqdm + +from django.db import migrations + +def assigned_time(model, request): + e = model.objects.filter(doc=request.doc, type="assigned_review_request", review_request=request).order_by('-time', '-id').first() + return e.time if e and e.time else None + +def done_time(model, request): + if request.unused_review and request.unused_review.time: + return request.unused_review.time + e = model.objects.filter(doc=request.doc, type="closed_review_request", review_request=request).order_by('-time', '-id').first() + time = e.time if e and e.time else None + return time if time else request.time + +def map_request_state_to_assignment_state(req_state_id): + if req_state_id == 'requested': + return 'assigned' + elif req_state_id in ('no-review-document', 'no-review-version'): + return 'withdrawn' + else: + return req_state_id + +def forward(apps, schema_editor): + ReviewRequest = apps.get_model('review', 'ReviewRequest') + ReviewAssignment = apps.get_model('review', 'ReviewAssignment') + Document = apps.get_model('doc', 'Document') + ReviewRequestDocEvent = apps.get_model('doc','ReviewRequestDocEvent') + ReviewAssignmentDocEvent = apps.get_model('doc','ReviewAssignmentDocEvent') + + print('') # introduce a newline before tqdm starts writing + + for request in tqdm(ReviewRequest.objects.exclude(unused_reviewer__isnull=True)): + assignment_state = map_request_state_to_assignment_state(request.state_id) + if not (assignment_state in (u'assigned', u'accepted', u'completed', u'no-response', u'overtaken', u'part-completed', u'rejected', u'withdrawn', u'unknown')): + print ("Trouble with review_request",request.pk,"with state",request.state_id) + exit(-1) + ReviewAssignment.objects.create( + review_request = request, + state_id = assignment_state, + reviewer = request.unused_reviewer, + assigned_on = assigned_time(ReviewRequestDocEvent, request), + completed_on = done_time(ReviewRequestDocEvent, request), + review = request.unused_review, + reviewed_rev = request.unused_reviewed_rev, + result = request.unused_result, + mailarch_url = request.unused_review and request.unused_review.external_url, + ) + Document.objects.filter(type_id='review').update(external_url='') + ReviewRequest.objects.filter(state_id__in=('accepted', 'rejected', 'no-response', 'part-completed', 'completed', 'unknown')).update(state_id='assigned') + ReviewRequest.objects.filter(state_id='requested',unused_reviewer__isnull=False).update(state_id='assigned') + + for req_event in tqdm(ReviewRequestDocEvent.objects.filter(type="assigned_review_request",review_request__unused_reviewer__isnull=False)): + ReviewAssignmentDocEvent.objects.create( + time = req_event.time, + type = req_event.type, + by = req_event.by, + doc = req_event.doc, + rev = req_event.rev, + desc = req_event.desc, + review_assignment = req_event.review_request.reviewassignment_set.first(), + state_id = 'assigned' + ) + + for req_event in tqdm(ReviewRequestDocEvent.objects.filter(type="closed_review_request", + state_id__in=('completed', 'no-response', 'part-completed', 'rejected', 'unknown', 'withdrawn'), + review_request__unused_reviewer__isnull=False)): + ReviewAssignmentDocEvent.objects.create( + time = req_event.time, + type = req_event.type, + by = req_event.by, + doc = req_event.doc, + rev = req_event.rev, + desc = req_event.desc, + review_assignment = req_event.review_request.reviewassignment_set.first(), + state_id = req_event.state_id + ) + + ReviewRequestDocEvent.objects.filter(type="closed_review_request", + state_id__in=('completed', 'no-response', 'part-completed', 'rejected', 'unknown', 'withdrawn'), + review_request__unused_reviewer__isnull=False).delete() + + +def reverse(apps, schema_editor): + ReviewAssignment = apps.get_model('review', 'ReviewAssignment') + ReviewRequestDocEvent = apps.get_model('doc','ReviewRequestDocEvent') + ReviewAssignmentDocEvent = apps.get_model('doc','ReviewAssignmentDocEvent') + + for assignment in tqdm(ReviewAssignment.objects.all()): + assignment.review_request.unused_review.external_url = assignment.mailarch_url + assignment.review_request.unused_review.save() + assignment.review_request.state_id = assignment.state_id + assignment.review_request.save() + + for asgn_event in tqdm(ReviewAssignmentDocEvent.objects.filter(state_id__in=('completed', 'no-response', 'part-completed', 'rejected', 'unknown', 'withdrawn'))): + ReviewRequestDocEvent.objects.create( + time = asgn_event.time, + type = asgn_event.type, + by = asgn_event.by, + doc = asgn_event.doc, + rev = asgn_event.rev, + desc = asgn_event.desc, + review_request = asgn_event.review_request, + state_id = asgn_event.state_id + ) + ReviewAssignmentDocEvent.objects.all().delete() + +class Migration(migrations.Migration): + + dependencies = [ + ('review', '0009_refactor_review_request'), + ('doc','0011_reviewassignmentdocevent') + ] + + operations = [ + migrations.RunPython(forward, reverse) + ]