diff --git a/per/admin.py b/per/admin.py index c19612c73..db2966d97 100644 --- a/per/admin.py +++ b/per/admin.py @@ -68,7 +68,11 @@ def area_number(self, obj): def get_queryset(self, request): return ( - super().get_queryset(request).order_by("area__area_num", "component_num", "component_letter").select_related("area") + super() + .get_queryset(request) + .exclude(component_num=14, is_parent__isnull=True) + .order_by("area__area_num", "component_num", "component_letter") + .select_related("area") ) diff --git a/per/drf_views.py b/per/drf_views.py index 00cc257f8..108a0ecbd 100644 --- a/per/drf_views.py +++ b/per/drf_views.py @@ -170,11 +170,19 @@ class FormAreaViewset(viewsets.ReadOnlyModelViewSet): class FormComponentFilter(filters.FilterSet): area_id = filters.NumberFilter(field_name="area__id", lookup_expr="exact") + exclude_subcomponents = filters.BooleanFilter( + method="get_exclude_subcomponents", + ) class Meta: model = FormComponent fields = {"area": ("exact",)} + def get_exclude_subcomponents(self, queryset, name, value): + if value: + return queryset.exclude(component_num=14, is_parent__isnull=True) + return queryset + class FormComponentViewset(viewsets.ReadOnlyModelViewSet): """PER Form Components Viewset""" diff --git a/per/management/commands/migrate_sub_components_to_component14.py b/per/management/commands/migrate_sub_components_to_component14.py new file mode 100644 index 000000000..5df9f3ff5 --- /dev/null +++ b/per/management/commands/migrate_sub_components_to_component14.py @@ -0,0 +1,73 @@ +from django.core.management.base import BaseCommand + +from per.models import FormComponent, OpsLearning + + +class Command(BaseCommand): + help = "Migration of sub components of component 14 to component 14" + + def handle(self, *args, **kwargs): + + parent_component_14 = FormComponent.objects.filter(component_num=14, is_parent=True).first() + + if not parent_component_14: + self.stdout.write(self.style.ERROR("No parent component found for component 14")) + return + + sub_components_14_ids = FormComponent.objects.filter(component_num=14, is_parent__isnull=True).values_list( + "id", flat=True + ) + + if not sub_components_14_ids.exists(): + self.stdout.write(self.style.ERROR("No sub components found for component 14")) + return + + # Get OpsLearning IDs that already have parent component + with_parent_component_ops_learning_qs = OpsLearning.objects.filter(per_component=parent_component_14).values_list( + "id", flat=True + ) + + # For per_component + # Removing if already have parent component + print( + OpsLearning.per_component.through.objects.filter( + formcomponent_id__in=sub_components_14_ids, opslearning_id__in=with_parent_component_ops_learning_qs + ).delete() + ) + + # Removing all Sub-Components except one and updating to parent component + OpsLearning.per_component.through.objects.filter(formcomponent_id__in=sub_components_14_ids).exclude( + id__in=OpsLearning.per_component.through.objects.filter(formcomponent_id__in=sub_components_14_ids).distinct( + "opslearning_id" + ) + ).delete() + + OpsLearning.per_component.through.objects.filter(formcomponent_id__in=sub_components_14_ids).update( + formcomponent_id=parent_component_14.id + ) + + # For per_component_validated + # Get OpsLearning IDs that already have parent component validated + with_parent_component_validated_ops_learning_qs = OpsLearning.objects.filter( + per_component_validated=parent_component_14 + ).values_list("id", flat=True) + + # Removing if already have parent component + print( + OpsLearning.per_component_validated.through.objects.filter( + formcomponent_id__in=sub_components_14_ids, opslearning_id__in=with_parent_component_validated_ops_learning_qs + ).delete() + ) + + # Removing all Sub-Components except one and updating to parent component + OpsLearning.per_component_validated.through.objects.filter(formcomponent_id__in=sub_components_14_ids).exclude( + id__in=OpsLearning.per_component_validated.through.objects.filter( + formcomponent_id__in=sub_components_14_ids + ).distinct("opslearning_id") + ).delete() + + OpsLearning.per_component_validated.through.objects.filter(formcomponent_id__in=sub_components_14_ids).update( + formcomponent_id=parent_component_14.id + ) + + self.stdout.write(self.style.SUCCESS("Successfully migrated sub-components of component-14 to component-14")) diff --git a/per/test_views.py b/per/test_views.py index f113d2f4b..8f328ee31 100644 --- a/per/test_views.py +++ b/per/test_views.py @@ -1,6 +1,8 @@ import json from unittest import mock +from django.core import management + from api.factories.country import CountryFactory from api.factories.region import RegionFactory from api.models import AppealType @@ -286,3 +288,63 @@ def test_ops_learning_stats(self): sources_overtime = response.data["sources_overtime"] self.assertEqual(len(sources_overtime), 2) + + def test_migrate_subcomponents(self): + parent_component_14 = FormComponentFactory.create(component_num=14, is_parent=True) + + sub_components_14 = FormComponentFactory.create_batch(3, component_num=14) + other_components = FormComponentFactory.create_batch(2, component_num=1) + + # OpsLearning with only parent component and no sub components of component 14 + ops_learning_with_only_parent_component = OpsLearningFactory.create() + ops_learning_with_only_parent_component.per_component.add(parent_component_14) + ops_learning_with_only_parent_component.per_component.add(*other_components) + + ops_learning_with_only_parent_component.per_component_validated.add(parent_component_14) + ops_learning_with_only_parent_component.per_component_validated.add(*other_components) + + # OpsLearning with parent component and sub components + ops_learning_with_parent_component = OpsLearningFactory.create() + + ops_learning_with_parent_component.per_component.add(parent_component_14) + ops_learning_with_parent_component.per_component.add(*sub_components_14) + ops_learning_with_parent_component.per_component.add(*other_components) + + ops_learning_with_parent_component.per_component_validated.add(parent_component_14) + ops_learning_with_parent_component.per_component_validated.add(*sub_components_14) + ops_learning_with_parent_component.per_component_validated.add(*other_components) + + # OpsLearning without parent component but with sub components + ops_learning_without_parent_component = OpsLearningFactory.create() + ops_learning_without_parent_component.per_component.add(*sub_components_14) + ops_learning_without_parent_component.per_component.add(*other_components) + + ops_learning_without_parent_component.per_component_validated.add(*sub_components_14) + ops_learning_without_parent_component.per_component_validated.add(*other_components) + + # Operational learning with one sub component without parent component + ops_learning = OpsLearningFactory.create() + ops_learning.per_component.add(sub_components_14[0]) + ops_learning.per_component_validated.add(sub_components_14[0]) + ops_learning.per_component_validated.add(sub_components_14[1]) + ops_learning.per_component.add(other_components[0]) + ops_learning.per_component_validated.add(other_components[0]) + + # Run the management command + management.call_command("migrate_sub_components_to_component14") + + ops_learning_with_only_parent_component.refresh_from_db() + self.assertEqual(ops_learning_with_only_parent_component.per_component.count(), 3) + self.assertEqual(ops_learning_with_only_parent_component.per_component_validated.count(), 3) + + ops_learning_with_parent_component.refresh_from_db() + self.assertEqual(ops_learning_with_parent_component.per_component.count(), 3) + self.assertEqual(ops_learning_with_parent_component.per_component_validated.count(), 3) + + ops_learning_without_parent_component.refresh_from_db() + self.assertEqual(ops_learning_without_parent_component.per_component.count(), 3) + self.assertEqual(ops_learning_without_parent_component.per_component_validated.count(), 3) + + ops_learning.refresh_from_db() + self.assertEqual(ops_learning.per_component.count(), 2) + self.assertEqual(ops_learning.per_component_validated.count(), 2)