From e771f047a1e096a08a98ff3da693fa0dec8aef1c Mon Sep 17 00:00:00 2001
From: Fredrik Jonsson <frjo@xdeb.org>
Date: Thu, 16 Aug 2018 10:02:47 +0200
Subject: [PATCH] Added migrate_*_determination commands.

---
 .../determinations/management/__init__.py     |   0
 .../management/commands/__init__.py           |   0
 .../migrate_concept_determinations.py         | 157 +++++++++++++
 .../migrate_proposal_determinations.py        | 217 ++++++++++++++++++
 4 files changed, 374 insertions(+)
 create mode 100644 opentech/apply/determinations/management/__init__.py
 create mode 100644 opentech/apply/determinations/management/commands/__init__.py
 create mode 100644 opentech/apply/determinations/management/commands/migrate_concept_determinations.py
 create mode 100644 opentech/apply/determinations/management/commands/migrate_proposal_determinations.py

diff --git a/opentech/apply/determinations/management/__init__.py b/opentech/apply/determinations/management/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/opentech/apply/determinations/management/commands/__init__.py b/opentech/apply/determinations/management/commands/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/opentech/apply/determinations/management/commands/migrate_concept_determinations.py b/opentech/apply/determinations/management/commands/migrate_concept_determinations.py
new file mode 100644
index 000000000..0d72d5161
--- /dev/null
+++ b/opentech/apply/determinations/management/commands/migrate_concept_determinations.py
@@ -0,0 +1,157 @@
+import argparse
+import json
+
+from datetime import datetime, timezone
+
+from django.contrib.auth import get_user_model
+from django.core.management.base import BaseCommand
+from django.db import transaction
+from django.db.utils import IntegrityError
+
+from opentech.apply.determinations.models import Determination
+from opentech.apply.funds.models import ApplicationSubmission
+
+
+class Command(BaseCommand):
+    help = "Determination migration script. Requires a source JSON file."
+    data = []
+
+    STREAMFIELD_MAP = {
+        "field_cnr_technical": {
+            "id": "technical",
+            "type": "value",
+            "key": "safe_value",
+        },
+        "field_cnr_principles": {
+            "id": "principles",
+            "type": "value",
+            "key": "safe_value",
+        },
+        "field_cnr_sustainable": {
+            "id": "sustainable",
+            "type": "value",
+            "key": "safe_value",
+        },
+        "field_cnsr_determination_message": {
+            "id": "message",
+            "type": "value",
+            "key": "safe_value",
+        },
+    }
+
+    def add_arguments(self, parser):
+        parser.add_argument('source', type=argparse.FileType('r'), help='Migration source JSON file')
+
+    @transaction.atomic
+    def handle(self, *args, **options):
+        with options['source'] as json_data:
+            self.data = json.load(json_data)
+
+            for id in self.data:
+                self.process(id)
+
+    def process(self, id):
+        node = self.data[id]
+        form_data = {}
+
+        for field in node:
+            if field in self.STREAMFIELD_MAP:
+                try:
+                    id = self.STREAMFIELD_MAP[field]['id']
+                    if id != 'message':
+                        form_data[id] = self.get_field_value(field, node)
+                except TypeError:
+                    pass
+
+        determination = Determination.objects.create(
+            created_at=datetime.fromtimestamp(int(node['created']), timezone.utc),
+            updated_at=datetime.fromtimestamp(int(node['changed']), timezone.utc),
+            author=self.get_user(node['uid']),
+            submission=self.get_submission(node['nid']),
+            outcome=self.get_workflow_state(node),
+            message=self.get_field_value('field_cnsr_determination_message', node),
+            data=form_data,
+        )
+
+        try:
+            determination.save()
+            self.stdout.write(f"Processed \"{node['title']}\" ({node['nid']})")
+        except IntegrityError:
+            self.stdout.write(f"Skipped \"{node['title']}\" ({node['nid']}) due to IntegrityError")
+            pass
+
+    def get_field_value(self, field, node):
+        """
+        Handles the following formats:
+        field: {(safe_)value: VALUE}
+        field: {target_id: ID} -- Drupal ForeignKey. Reference to other node or user entities.
+        field: {tid: ID} -- or term ID. fk to Categories
+        field: []
+        field: [{value|target_id|tid: VALUE},]
+        """
+        mapping = self.STREAMFIELD_MAP[field]
+        mapping_type = mapping['type']
+        key = mapping.get('key', 'value')
+        source_value = node[field]
+        value = None
+
+        if mapping_type == "direct":
+            value = source_value
+        elif mapping_type == 'value':
+            value = self.nl2br(source_value[key]) if source_value else ''
+        elif mapping_type == 'map' and 'map' in 'mapping':
+            value = mapping['map'].get(source_value[key])
+        elif mapping_type == 'address' and 'map' in mapping:
+            try:
+                value_map = mapping['map']
+                value = {}
+                for item in value_map:
+                    value[value_map[item]] = source_value[item]
+            except TypeError:
+                value = {}
+        elif mapping_type == 'boolean':
+            value = source_value[key] == '1' if source_value else False
+        elif mapping_type == 'category':
+            if not source_value:
+                value = []
+            else:
+                if isinstance(source_value, dict):
+                    option = self.get_referenced_term(source_value[key])
+                    value = [option] if option else []
+                else:
+                    value = []
+                    for item in source_value:
+                        option = self.get_referenced_term(item[key])
+                        if option:
+                            value.append(option)
+        elif mapping_type == 'file':
+            # TODO finish mapping. Requires access to the files.
+            value = {}
+
+        return value
+
+    def get_user(self, uid):
+        try:
+            User = get_user_model()
+            return User.objects.get(drupal_id=uid)
+        except User.DoesNotExist:
+            return None
+
+    def get_submission(self, nid):
+        try:
+            return ApplicationSubmission.objects.get(drupal_id=nid)
+        except ApplicationSubmission.DoesNotExist:
+            return 'None'
+
+    def get_determination(self, node):
+        choices = {
+            "invited": 2,
+            "approved": 2,
+            "dropped": 0,
+            "undetermined": 1
+        }
+
+        return choices.get(node['field_cnsr_determination']['value'], 1)
+
+    def nl2br(self, value):
+        return value.replace('\r\n', '<br>\n')
diff --git a/opentech/apply/determinations/management/commands/migrate_proposal_determinations.py b/opentech/apply/determinations/management/commands/migrate_proposal_determinations.py
new file mode 100644
index 000000000..4fac63c8b
--- /dev/null
+++ b/opentech/apply/determinations/management/commands/migrate_proposal_determinations.py
@@ -0,0 +1,217 @@
+import argparse
+import json
+
+from datetime import datetime, timezone
+
+from django.contrib.auth import get_user_model
+from django.core.management.base import BaseCommand
+from django.db import transaction
+from django.db.utils import IntegrityError
+
+from opentech.apply.determinations.models import Determination
+from opentech.apply.funds.models import ApplicationSubmission
+
+
+class Command(BaseCommand):
+    help = "Determination migration script. Requires a source JSON file."
+    data = []
+
+    STREAMFIELD_MAP = {
+        "field_pr_liked": {
+            "id": "liked",
+            "type": "value",
+            "key": "safe_value",
+        },
+        "field_pr_realism": {
+            "id": "realism",
+            "type": "value",
+            "key": "safe_value",
+        },
+        "field_pr_concern": {
+            "id": "concerns",
+            "type": "value",
+            "key": "safe_value",
+        },
+        "field_pr_overview": {
+            "id": "overview",
+            "type": "value",
+            "key": "safe_value",
+        },
+        "field_pr_strategy": {
+            "id": "strategy",
+            "type": "value",
+            "key": "safe_value",
+        },
+        "field_pr_rationale": {
+            "id": "rationale",
+            "type": "value",
+            "key": "safe_value",
+        },
+        "field_pr_red_flags": {
+            "id": "red_flags",
+            "type": "value",
+            "key": "safe_value",
+        },
+        "field_pr_technical": {
+            "id": "technical",
+            "type": "value",
+            "key": "safe_value",
+        },
+        "field_pr_usability": {
+            "id": "usability",
+            "type": "value",
+            "key": "safe_value",
+        },
+        "field_pr_evaluation": {
+            "id": "evaluation",
+            "type": "value",
+            "key": "safe_value",
+        },
+        "field_pr_objectives": {
+            "id": "objectives",
+            "type": "value",
+            "key": "safe_value",
+        },
+        "field_pr_alternative": {
+            "id": "alternative",
+            "type": "value",
+            "key": "safe_value",
+        },
+        "field_pr_collaboration": {
+            "id": "collaboration",
+            "type": "value",
+            "key": "safe_value",
+        },
+        "field_pr_qualifications": {
+            "id": "qualifications",
+            "type": "value",
+            "key": "safe_value",
+        },
+        "field_pr_sustainability": {
+            "id": "sustainability",
+            "type": "value",
+            "key": "safe_value",
+        },
+        "field_psr_determination_message": {
+            "id": "message",
+            "type": "value",
+            "key": "safe_value",
+        },
+    }
+
+    def add_arguments(self, parser):
+        parser.add_argument('source', type=argparse.FileType('r'), help='Migration source JSON file')
+
+    @transaction.atomic
+    def handle(self, *args, **options):
+        with options['source'] as json_data:
+            self.data = json.load(json_data)
+
+            for id in self.data:
+                self.process(id)
+
+    def process(self, id):
+        node = self.data[id]
+        form_data = {}
+
+        for field in node:
+            if field in self.STREAMFIELD_MAP:
+                try:
+                    id = self.STREAMFIELD_MAP[field]['id']
+                    if id != 'message':
+                        form_data[id] = self.get_field_value(field, node)
+                except TypeError:
+                    pass
+
+        determination = Determination.objects.create(
+            created_at=datetime.fromtimestamp(int(node['created']), timezone.utc),
+            updated_at=datetime.fromtimestamp(int(node['changed']), timezone.utc),
+            author=self.get_user(node['uid']),
+            submission=self.get_submission(node['nid']),
+            outcome=self.get_workflow_state(node),
+            message=self.get_field_value('field_psr_determination_message', node),
+            data=form_data,
+        )
+
+        try:
+            determination.save()
+            self.stdout.write(f"Processed \"{node['title']}\" ({node['nid']})")
+        except IntegrityError:
+            self.stdout.write(f"Skipped \"{node['title']}\" ({node['nid']}) due to IntegrityError")
+            pass
+
+    def get_field_value(self, field, node):
+        """
+        Handles the following formats:
+        field: {(safe_)value: VALUE}
+        field: {target_id: ID} -- Drupal ForeignKey. Reference to other node or user entities.
+        field: {tid: ID} -- or term ID. fk to Categories
+        field: []
+        field: [{value|target_id|tid: VALUE},]
+        """
+        mapping = self.STREAMFIELD_MAP[field]
+        mapping_type = mapping['type']
+        key = mapping.get('key', 'value')
+        source_value = node[field]
+        value = None
+
+        if mapping_type == "direct":
+            value = source_value
+        elif mapping_type == 'value':
+            value = self.nl2br(source_value[key]) if source_value else ''
+        elif mapping_type == 'map' and 'map' in 'mapping':
+            value = mapping['map'].get(source_value[key])
+        elif mapping_type == 'address' and 'map' in mapping:
+            try:
+                value_map = mapping['map']
+                value = {}
+                for item in value_map:
+                    value[value_map[item]] = source_value[item]
+            except TypeError:
+                value = {}
+        elif mapping_type == 'boolean':
+            value = source_value[key] == '1' if source_value else False
+        elif mapping_type == 'category':
+            if not source_value:
+                value = []
+            else:
+                if isinstance(source_value, dict):
+                    option = self.get_referenced_term(source_value[key])
+                    value = [option] if option else []
+                else:
+                    value = []
+                    for item in source_value:
+                        option = self.get_referenced_term(item[key])
+                        if option:
+                            value.append(option)
+        elif mapping_type == 'file':
+            # TODO finish mapping. Requires access to the files.
+            value = {}
+
+        return value
+
+    def get_user(self, uid):
+        try:
+            User = get_user_model()
+            return User.objects.get(drupal_id=uid)
+        except User.DoesNotExist:
+            return None
+
+    def get_submission(self, nid):
+        try:
+            return ApplicationSubmission.objects.get(drupal_id=nid)
+        except ApplicationSubmission.DoesNotExist:
+            return 'None'
+
+    def get_determination(self, node):
+        choices = {
+            "invited": 2,
+            "approved": 2,
+            "dropped": 0,
+            "undetermined": 1
+        }
+
+        return choices.get(node['field_psr_determination']['value'], 1)
+
+    def nl2br(self, value):
+        return value.replace('\r\n', '<br>\n')
-- 
GitLab