From 090f3d0dee5ea321daf6522a80f00c4d7589b836 Mon Sep 17 00:00:00 2001
From: Kevin Carrogan <kevin@carrogan.com>
Date: Wed, 1 May 2024 10:12:28 +0100
Subject: [PATCH 1/2] Use bulk updates and creates for denials migrations

---
 .../migrations/0023_set_denial_entity_type.py | 42 ++++-----
 .../migrations/0024_denials_data_migration.py | 86 ++++++++++---------
 2 files changed, 69 insertions(+), 59 deletions(-)

diff --git a/api/external_data/migrations/0023_set_denial_entity_type.py b/api/external_data/migrations/0023_set_denial_entity_type.py
index 253ad6483..82550e794 100644
--- a/api/external_data/migrations/0023_set_denial_entity_type.py
+++ b/api/external_data/migrations/0023_set_denial_entity_type.py
@@ -5,38 +5,40 @@
 
 
 def get_denial_entity_type(data):
+    if not isinstance(data, dict):
+        return
 
-    if isinstance(data, dict):
-        entity_type = ""
-        normalised_entity_type_dict = {keys.lower(): values.lower() for keys, values in data.items()}
+    entity_type = ""
+    normalised_entity_type_dict = {keys.lower(): values.lower() for keys, values in data.items()}
 
-        is_end_user_flag = normalised_entity_type_dict.get("end_user_flag", "false") == "true"
-        is_consignee_flag = normalised_entity_type_dict.get("consignee_flag", "false") == "true"
-        is_other_role = len(normalised_entity_type_dict.get("other_role", "")) > 0
+    is_end_user_flag = normalised_entity_type_dict.get("end_user_flag", "false") == "true"
+    is_consignee_flag = normalised_entity_type_dict.get("consignee_flag", "false") == "true"
+    is_other_role = len(normalised_entity_type_dict.get("other_role", "")) > 0
 
-        if is_end_user_flag and is_consignee_flag:
-            entity_type = DenialEntityType.END_USER
-        elif not is_end_user_flag and is_consignee_flag:
-            entity_type = DenialEntityType.CONSIGNEE
-        elif is_end_user_flag and not is_consignee_flag:
-            entity_type = DenialEntityType.END_USER
-        elif not is_end_user_flag and not is_consignee_flag and is_other_role:
-            entity_type = DenialEntityType.THIRD_PARTY
+    if is_end_user_flag and is_consignee_flag:
+        entity_type = DenialEntityType.END_USER
+    elif not is_end_user_flag and is_consignee_flag:
+        entity_type = DenialEntityType.CONSIGNEE
+    elif is_end_user_flag and not is_consignee_flag:
+        entity_type = DenialEntityType.END_USER
+    elif not is_end_user_flag and not is_consignee_flag and is_other_role:
+        entity_type = DenialEntityType.THIRD_PARTY
 
-        return entity_type
+    return entity_type
 
 
 def set_denial_entity_type(apps, schema_editor):
-
     DenialEntity = apps.get_model("external_data", "DenialEntity")
 
-    for denial_entity in DenialEntity.objects.filter(entity_type__isnull=True):
+    denials_to_update = []
 
+    for denial_entity in DenialEntity.objects.filter(entity_type__isnull=True):
         denial_entity_type = get_denial_entity_type(denial_entity.data)
-
-        if denial_entity_type in ["end_user", "consignee", "third_party"]:
+        if denial_entity_type is not None and denial_entity_type in ["end_user", "consignee", "third_party"]:
             denial_entity.entity_type = denial_entity_type
-            denial_entity.save()
+            denials_to_update.append(denial_entity)
+
+    DenialEntity.objects.bulk_update(denials_to_update, ["entity_type"])
 
 
 class Migration(migrations.Migration):
diff --git a/api/external_data/migrations/0024_denials_data_migration.py b/api/external_data/migrations/0024_denials_data_migration.py
index c579082db..7a6dc081c 100644
--- a/api/external_data/migrations/0024_denials_data_migration.py
+++ b/api/external_data/migrations/0024_denials_data_migration.py
@@ -1,65 +1,73 @@
 # Generated by Django 4.2.10 on 2024-04-02 14:59
 
 import logging
-from django.db import migrations
-from django.forms.models import model_to_dict
+
+from django.db import IntegrityError, migrations, transaction
 from django.db.models import Q
-from django.db import IntegrityError
-from django.db import transaction
-from django.conf import settings
+from django.forms.models import model_to_dict
+
+
 log = logging.getLogger(__name__)
 
+
 required_fields = [
-        "reference",
-        "regime_reg_ref",
-        "notifying_government",
-        "item_list_codes",
-        "item_description",
-        "end_use",
-        "is_revoked",
-        "is_revoked_comment",
-        "reason_for_refusal",
+    "reference",
+    "regime_reg_ref",
+    "notifying_government",
+    "item_list_codes",
+    "item_description",
+    "end_use",
+    "is_revoked",
+    "is_revoked_comment",
+    "reason_for_refusal",
 ]
 
-def denials_data_migration(apps, schema_editor):
-    settings.ELASTICSEARCH_DSL_AUTOSYNC = False
 
+def denials_data_migration(apps, schema_editor):
     DenialEntity = apps.get_model("external_data", "DenialEntity")
     Denial = apps.get_model("external_data", "Denial")
 
     # There are a handfull (10) of regime_reg_refs that are null which was in the initial load
-    # Assumption here is that they can be deleted since it's erroneous data as we now know 
-    # regime_reg_ref is considered a unique DN record. 
+    # Assumption here is that they can be deleted since it's erroneous data as we now know
+    # regime_reg_ref is considered a unique DN record.
 
-    total_null_regime_reg_ref =  DenialEntity.objects.filter(Q(regime_reg_ref__isnull=True) | Q(regime_reg_ref=''))
+    total_null_regime_reg_ref = DenialEntity.objects.filter(Q(regime_reg_ref__isnull=True) | Q(regime_reg_ref=""))
     log.info(
-            "Delete null regime_reg_ref total -> %s",
-            total_null_regime_reg_ref.count(),
+        "Delete null regime_reg_ref total -> %s",
+        total_null_regime_reg_ref.count(),
     )
     total_null_regime_reg_ref.delete()
-    duplicate_denial_errors = []
+
     with transaction.atomic():
         sid = transaction.savepoint()
+        denials_to_create = []
+        denials_to_update = []
         for denial_entity in DenialEntity.objects.all():
+            denial_entity_dict = {
+                key: value for (key, value) in model_to_dict(denial_entity).items() if key in required_fields
+            }
+
             try:
-                denial_entity_dict = {key:value for (key,value) in model_to_dict(denial_entity).items() if key in required_fields}
-                denial , _ = Denial.objects.get_or_create(**denial_entity_dict)
-                denial_entity.denial = denial
-                denial_entity.save()
-            except IntegrityError as e:
-                    duplicate_denial_errors.append(denial_entity.regime_reg_ref)
-        
-        if duplicate_denial_errors:
-            log.info(
-                "There are the following duplicate denials in the database rolling back this migration: -> %s",
-                    duplicate_denial_errors,
-            )
+                denial = Denial.objects.get(**denial_entity_dict)
+            except Denial.DoesNotExist:
+                denial = Denial(**denial_entity_dict)
+                denials_to_create.append(denial)
+
+            denial_entity.denial = denial
+            denials_to_update.append(denial_entity)
+
+        try:
+            Denial.objects.bulk_create(denials_to_create)
+        except IntegrityError:
+            log.exception("There were errors creating denial objects")
             transaction.savepoint_rollback(sid)
-        else:
-            transaction.savepoint_commit(sid)
-     
-    settings.ELASTICSEARCH_DSL_AUTOSYNC = True
-    
+            return
+
+        DenialEntity.objects.bulk_update(denials_to_update, ["denial"])
+
+        transaction.savepoint_commit(sid)
+
+
 class Migration(migrations.Migration):
     dependencies = [
         ("external_data", "0023_set_denial_entity_type"),

From 0099de80a63e2799898230e04f3792983aa5d390 Mon Sep 17 00:00:00 2001
From: Kevin Carrogan <kevin@carrogan.com>
Date: Wed, 1 May 2024 10:56:50 +0100
Subject: [PATCH 2/2] fixup

---
 api/conf/settings.py                          |  2 +-
 .../migrations/0023_set_denial_entity_type.py | 42 ++++----
 .../migrations/0024_denials_data_migration.py | 49 ++++------
 .../tests/test_0024_denials_data_migration.py | 95 ++++++++++++-------
 4 files changed, 102 insertions(+), 86 deletions(-)

diff --git a/api/conf/settings.py b/api/conf/settings.py
index bc0cdfa5e..45988f022 100644
--- a/api/conf/settings.py
+++ b/api/conf/settings.py
@@ -342,7 +342,7 @@ def _build_redis_url(base_url, db_number, **query_args):
     }
 
     ENABLE_SPIRE_SEARCH = env.bool("ENABLE_SPIRE_SEARCH", False)
-    ELASTICSEARCH_DSL_AUTOSYNC = True
+
     ELASTICSEARCH_PRODUCT_INDEXES = {"LITE": ELASTICSEARCH_PRODUCT_INDEX_ALIAS}
     ELASTICSEARCH_APPLICATION_INDEXES = {"LITE": ELASTICSEARCH_APPLICATION_INDEX_ALIAS}
     SPIRE_APPLICATION_INDEX_NAME = env.str("SPIRE_APPLICATION_INDEX_NAME", "spire-application-alias")
diff --git a/api/external_data/migrations/0023_set_denial_entity_type.py b/api/external_data/migrations/0023_set_denial_entity_type.py
index 82550e794..253ad6483 100644
--- a/api/external_data/migrations/0023_set_denial_entity_type.py
+++ b/api/external_data/migrations/0023_set_denial_entity_type.py
@@ -5,40 +5,38 @@
 
 
 def get_denial_entity_type(data):
-    if not isinstance(data, dict):
-        return
 
-    entity_type = ""
-    normalised_entity_type_dict = {keys.lower(): values.lower() for keys, values in data.items()}
+    if isinstance(data, dict):
+        entity_type = ""
+        normalised_entity_type_dict = {keys.lower(): values.lower() for keys, values in data.items()}
 
-    is_end_user_flag = normalised_entity_type_dict.get("end_user_flag", "false") == "true"
-    is_consignee_flag = normalised_entity_type_dict.get("consignee_flag", "false") == "true"
-    is_other_role = len(normalised_entity_type_dict.get("other_role", "")) > 0
+        is_end_user_flag = normalised_entity_type_dict.get("end_user_flag", "false") == "true"
+        is_consignee_flag = normalised_entity_type_dict.get("consignee_flag", "false") == "true"
+        is_other_role = len(normalised_entity_type_dict.get("other_role", "")) > 0
 
-    if is_end_user_flag and is_consignee_flag:
-        entity_type = DenialEntityType.END_USER
-    elif not is_end_user_flag and is_consignee_flag:
-        entity_type = DenialEntityType.CONSIGNEE
-    elif is_end_user_flag and not is_consignee_flag:
-        entity_type = DenialEntityType.END_USER
-    elif not is_end_user_flag and not is_consignee_flag and is_other_role:
-        entity_type = DenialEntityType.THIRD_PARTY
+        if is_end_user_flag and is_consignee_flag:
+            entity_type = DenialEntityType.END_USER
+        elif not is_end_user_flag and is_consignee_flag:
+            entity_type = DenialEntityType.CONSIGNEE
+        elif is_end_user_flag and not is_consignee_flag:
+            entity_type = DenialEntityType.END_USER
+        elif not is_end_user_flag and not is_consignee_flag and is_other_role:
+            entity_type = DenialEntityType.THIRD_PARTY
 
-    return entity_type
+        return entity_type
 
 
 def set_denial_entity_type(apps, schema_editor):
-    DenialEntity = apps.get_model("external_data", "DenialEntity")
 
-    denials_to_update = []
+    DenialEntity = apps.get_model("external_data", "DenialEntity")
 
     for denial_entity in DenialEntity.objects.filter(entity_type__isnull=True):
+
         denial_entity_type = get_denial_entity_type(denial_entity.data)
-        if denial_entity_type is not None and denial_entity_type in ["end_user", "consignee", "third_party"]:
-            denial_entity.entity_type = denial_entity_type
-            denials_to_update.append(denial_entity)
 
-    DenialEntity.objects.bulk_update(denials_to_update, ["entity_type"])
+        if denial_entity_type in ["end_user", "consignee", "third_party"]:
+            denial_entity.entity_type = denial_entity_type
+            denial_entity.save()
 
 
 class Migration(migrations.Migration):
diff --git a/api/external_data/migrations/0024_denials_data_migration.py b/api/external_data/migrations/0024_denials_data_migration.py
index 7a6dc081c..5f18ea5f2 100644
--- a/api/external_data/migrations/0024_denials_data_migration.py
+++ b/api/external_data/migrations/0024_denials_data_migration.py
@@ -38,35 +38,26 @@ def denials_data_migration(apps, schema_editor):
     )
     total_null_regime_reg_ref.delete()
 
-    with transaction.atomic():
-        sid = transaction.savepoint()
-        denials_to_create = []
-        denials_to_update = []
-        for denial_entity in DenialEntity.objects.all():
-            denial_entity_dict = {
-                key: value for (key, value) in model_to_dict(denial_entity).items() if key in required_fields
-            }
-
-            try:
-                denial = Denial.objects.get(**denial_entity_dict)
-            except Denial.DoesNotExist:
-                denial = Denial(**denial_entity_dict)
-                denials_to_create.append(denial)
-
-            denial_entity.denial = denial
-            denials_to_update.append(denial_entity)
-
-        try:
-            Denial.objects.bulk_create(denials_to_create)
-        except IntegrityError:
-            log.exception("There were errors creating denial objects")
-            transaction.savepoint_rollback(sid)
-            return
-
-        DenialEntity.objects.bulk_update(denials_to_update, ["denial"])
-
-        transaction.savepoint_commit(sid)
-
+    denials_to_create = []
+    denials_entitiy_to_update = []
+    denials_dict_map = {}
+    for denial_entity in DenialEntity.objects.all():
+        denial_entity_dict = {
+            key: value for (key, value) in model_to_dict(denial_entity).items() if key in required_fields
+        }
+        denials_dict_map[denial_entity_dict["regime_reg_ref"]] = denial_entity_dict
+    
+    for denials_dict_map_item in denials_dict_map.values():
+        denial = Denial(**denials_dict_map_item)
+        denials_to_create.append(denial)
+
+    Denial.objects.bulk_create(denials_to_create)
+
+    for denial_entity in DenialEntity.objects.all():
+        denial_entity.denial = Denial.objects.get(regime_reg_ref=denial_entity.regime_reg_ref)
+        denials_entitiy_to_update.append(denial_entity)
+    
+    DenialEntity.objects.bulk_update(denials_entitiy_to_update, ["denial"])
 
 class Migration(migrations.Migration):
     dependencies = [
diff --git a/api/external_data/migrations/tests/test_0024_denials_data_migration.py b/api/external_data/migrations/tests/test_0024_denials_data_migration.py
index a17be6567..e1bfe8e0b 100644
--- a/api/external_data/migrations/tests/test_0024_denials_data_migration.py
+++ b/api/external_data/migrations/tests/test_0024_denials_data_migration.py
@@ -4,11 +4,65 @@
 
 
 test_data = [
-{"reference":"DN2010\/0057","regime_reg_ref":"reg.123.123","name":"name 1","address":"address 1","notifying_government":"UK","country":"UK","item_list_codes":"all","item_description":"desc a","end_use":"use 1","reason_for_refusal":"a"},
-{"reference":"DN2010\/0057","regime_reg_ref":"reg.123.1234","name":"name 2","address":"address 2","notifying_government":"UK","country":"UK","item_list_codes":"all","item_description":"desc a","end_use":"use 1","reason_for_refusal":"a"},
-{"reference":"DN2010\/0057","regime_reg_ref":"reg.123.1234","name":"name 3","address":"address 3","notifying_government":"UK","country":"UK","item_list_codes":"all","item_description":"desc a","end_use":"use 1","reason_for_refusal":"a"},
-{"reference":"DN2010\/0057","regime_reg_ref":"reg.123.1234","name":"name 4","address":"address 4","notifying_government":"UK","country":"UK","item_list_codes":"all","item_description":"desc a","end_use":"use 1","reason_for_refusal":"a"},
-{"reference":"DN2010\/0057","name":"bad record","address":"bad record","notifying_government":"UK","country":"bad","item_list_codes":"all","item_description":"bad","end_use":"bad","reason_for_refusal":"bad "}
+    {
+        "reference": "DN2010\/0057",
+        "regime_reg_ref": "reg.123.123",
+        "name": "name 1",
+        "address": "address 1",
+        "notifying_government": "UK",
+        "country": "UK",
+        "item_list_codes": "all",
+        "item_description": "desc a",
+        "end_use": "use 1",
+        "reason_for_refusal": "a",
+    },
+    {
+        "reference": "DN2010\/0057",
+        "regime_reg_ref": "reg.123.1234",
+        "name": "name 2",
+        "address": "address 2",
+        "notifying_government": "UK",
+        "country": "UK",
+        "item_list_codes": "all",
+        "item_description": "desc a",
+        "end_use": "use 1",
+        "reason_for_refusal": "a",
+    },
+    {
+        "reference": "DN2010\/0057",
+        "regime_reg_ref": "reg.123.1234",
+        "name": "name 3",
+        "address": "address 3",
+        "notifying_government": "UK",
+        "country": "UK",
+        "item_list_codes": "all",
+        "item_description": "desc a",
+        "end_use": "use 1",
+        "reason_for_refusal": "a",
+    },
+    {
+        "reference": "DN2010\/0057",
+        "regime_reg_ref": "reg.123.1234",
+        "name": "name 4",
+        "address": "address 4",
+        "notifying_government": "UK",
+        "country": "UK",
+        "item_list_codes": "all",
+        "item_description": "desc a",
+        "end_use": "use 1",
+        "reason_for_refusal": "a",
+    },
+    {
+        "reference": "DN2010\/0057",
+        "name": "bad record",
+        "address": "bad record",
+        "notifying_government": "UK",
+        "country": "bad",
+        "item_list_codes": "all",
+        "item_description": "bad",
+        "end_use": "bad",
+        "reason_for_refusal": "bad ",
+    },
 ]
 
 
@@ -18,14 +72,10 @@ class TestDenialDataMigration(MigratorTestCase):
     migrate_from = ("external_data", "0023_set_denial_entity_type")
     migrate_to = ("external_data", "0024_denials_data_migration")
 
-
     def prepare(self):
         DenialEntity = self.old_state.apps.get_model("external_data", "DenialEntity")
         for row in test_data:
-            DenialEntity.objects.create(**row)  
-
-    
-
+            DenialEntity.objects.create(**row)
 
     def test_0023_denials_data_migration(self):
         DenialEntity = self.new_state.apps.get_model("external_data", "DenialEntity")
@@ -33,27 +83,4 @@ def test_0023_denials_data_migration(self):
 
         self.assertEqual(DenialEntity.objects.all().count(), 4)
         self.assertEqual(Denial.objects.all().count(), 2)
-        self.assertEqual(Denial.objects.get(regime_reg_ref='reg.123.1234').denial_entity.count(), 3)
-
-
-@pytest.mark.django_db()
-class TestDenialDataDuplicatesMigration(MigratorTestCase):
-
-    migrate_from = ("external_data", "0023_set_denial_entity_type")
-    migrate_to = ("external_data", "0024_denials_data_migration")
-
-
-    def prepare(self):
-        DenialEntity = self.old_state.apps.get_model("external_data", "DenialEntity")
-        for row in test_data:
-            DenialEntity.objects.create(**row)
-        test_data[0]["end_use"] = "end_use b"
-        DenialEntity.objects.create(**test_data[0])
-    
-
-
-    def test_0024_denials_data_migration_duplicates(self):
-        DenialEntity = self.new_state.apps.get_model("external_data", "DenialEntity")
-        Denial = self.new_state.apps.get_model("external_data", "Denial")
-        self.assertEqual(DenialEntity.objects.all().count(), 5)
-        self.assertEqual(Denial.objects.all().count(), 0)
+        self.assertEqual(Denial.objects.get(regime_reg_ref="reg.123.1234").denial_entity.count(), 3)