From 64a41c9bc34e872c22e6bbac974bf5cd2f72ead8 Mon Sep 17 00:00:00 2001 From: sniedzielski <52816247+sniedzielski@users.noreply.github.com> Date: Fri, 29 Dec 2023 13:48:59 +0100 Subject: [PATCH 1/9] CM-381: Create change log tab for individual (#34) * CM-381: added history gql for individual * CM-381: added history gql filtering for individual * CM-381: revert migration file * CM-381: revert migration file 2 --- individual/gql_queries.py | 20 ++++++++++++++++++++ individual/schema.py | 22 +++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/individual/gql_queries.py b/individual/gql_queries.py index 28937fc..f84bd97 100644 --- a/individual/gql_queries.py +++ b/individual/gql_queries.py @@ -25,6 +25,26 @@ class Meta: connection_class = ExtendedConnection +class IndividualHistoryGQLType(DjangoObjectType): + uuid = graphene.String(source='uuid') + + class Meta: + model = Individual.history.model + interfaces = (graphene.relay.Node,) + filter_fields = { + "id": ["exact"], + "first_name": ["iexact", "istartswith", "icontains"], + "last_name": ["iexact", "istartswith", "icontains"], + "dob": ["exact", "lt", "lte", "gt", "gte"], + + "date_created": ["exact", "lt", "lte", "gt", "gte"], + "date_updated": ["exact", "lt", "lte", "gt", "gte"], + "is_deleted": ["exact"], + "version": ["exact"], + } + connection_class = ExtendedConnection + + class IndividualDataSourceUploadGQLType(DjangoObjectType): uuid = graphene.String(source='uuid') diff --git a/individual/schema.py b/individual/schema.py index 6e5654a..b7bd8bc 100644 --- a/individual/schema.py +++ b/individual/schema.py @@ -14,7 +14,7 @@ CreateGroupMutation, UpdateGroupMutation, DeleteGroupMutation, CreateGroupIndividualMutation, \ UpdateGroupIndividualMutation, DeleteGroupIndividualMutation, \ CreateGroupIndividualsMutation -from individual.gql_queries import IndividualGQLType, IndividualDataSourceGQLType, GroupGQLType, GroupIndividualGQLType, \ +from individual.gql_queries import IndividualGQLType, IndividualHistoryGQLType, IndividualDataSourceGQLType, GroupGQLType, GroupIndividualGQLType, \ IndividualDataSourceUploadGQLType, GroupHistoryGQLType from individual.models import Individual, IndividualDataSource, Group, GroupIndividual, IndividualDataSourceUpload @@ -57,6 +57,14 @@ class Query(ExportableQueryMixin, graphene.ObjectType): customFilters=graphene.List(of_type=graphene.String) ) + individual_history = OrderedDjangoFilterConnectionField( + IndividualHistoryGQLType, + orderBy=graphene.List(of_type=graphene.String), + applyDefaultValidityFilter=graphene.Boolean(), + client_mutation_id=graphene.String(), + groupId=graphene.String() + ) + individual_data_source = OrderedDjangoFilterConnectionField( IndividualDataSourceGQLType, orderBy=graphene.List(of_type=graphene.String), @@ -124,6 +132,18 @@ def resolve_individual(self, info, **kwargs): ) return gql_optimizer.query(query, info) + def resolve_individual_history(self, info, **kwargs): + filters = append_validity_filter(**kwargs) + + client_mutation_id = kwargs.get("client_mutation_id") + if client_mutation_id: + filters.append(Q(mutations__mutation__client_mutation_id=client_mutation_id)) + + Query._check_permissions(info.context.user, + IndividualConfig.gql_individual_search_perms) + query = Individual.history.filter(*filters) + return gql_optimizer.query(query, info) + def resolve_individual_data_source(self, info, **kwargs): filters = append_validity_filter(**kwargs) From ec30ebc8a93568a525dbce82a9bf2a208c024bde Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 3 Jan 2024 14:28:54 +0100 Subject: [PATCH 2/9] CM-407: add head to group json_ext (#36) Co-authored-by: Jan --- individual/services.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/individual/services.py b/individual/services.py index efe887e..f429758 100644 --- a/individual/services.py +++ b/individual/services.py @@ -4,6 +4,7 @@ from core.services import BaseService from core.signals import register_service_signal +from django.utils.translation import gettext as _ from individual.models import Individual, IndividualDataSource, GroupIndividual, Group from individual.validation import IndividualValidation, IndividualDataSourceValidation, GroupIndividualValidation, \ GroupValidation @@ -121,8 +122,37 @@ def create(self, obj_data): return super().create(obj_data) @register_service_signal('group_individual.update') + @check_authentication def update(self, obj_data): - return super().update(obj_data) + try: + with transaction.atomic(): + obj_data = self._adjust_update_payload(obj_data) + self.validation_class.validate_update(self.user, **obj_data) + obj_ = self.OBJECT_TYPE.objects.filter(id=obj_data['id']).first() + self._handle_change_head(obj_data) + [setattr(obj_, key, obj_data[key]) for key in obj_data] + result = self.save_instance(obj_) + self._handle_group_update(obj_) + return result + except Exception as exc: + return output_exception(model_name=self.OBJECT_TYPE.__name__, method="update", exception=exc) + + def _handle_change_head(self, obj_data): + group_id = obj_data.get('group_id') + group_queryset = GroupIndividual.objects.filter(group_id=group_id, role=GroupIndividual.Role.HEAD) + old_head = group_queryset.first() + if old_head: + old_head.role = GroupIndividual.Role.RECIPIENT + old_head.save(username=self.user.username) + + if group_queryset.exists(): + raise ValueError(_("more_than_one_head_in_group")) + + def _handle_group_update(self, obj_): + group = Group.objects.filter(groupindividual=obj_).first() + if group: + group.json_ext['head'] = f'{obj_.individual.first_name} {obj_.individual.last_name}' + group.save(username=self.user.username) @register_service_signal('group_individual.delete') def delete(self, obj_data): From c2ea919181104c51ab07e3a0e149188d1b501f53 Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 5 Jan 2024 09:44:19 +0100 Subject: [PATCH 3/9] CM-404: update handle change head (#37) Co-authored-by: Jan --- individual/services.py | 46 ++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/individual/services.py b/individual/services.py index f429758..5f306e1 100644 --- a/individual/services.py +++ b/individual/services.py @@ -129,30 +129,36 @@ def update(self, obj_data): obj_data = self._adjust_update_payload(obj_data) self.validation_class.validate_update(self.user, **obj_data) obj_ = self.OBJECT_TYPE.objects.filter(id=obj_data['id']).first() - self._handle_change_head(obj_data) + self._handle_head_change(obj_data, obj_) [setattr(obj_, key, obj_data[key]) for key in obj_data] - result = self.save_instance(obj_) - self._handle_group_update(obj_) - return result + return self.save_instance(obj_) except Exception as exc: return output_exception(model_name=self.OBJECT_TYPE.__name__, method="update", exception=exc) - def _handle_change_head(self, obj_data): - group_id = obj_data.get('group_id') - group_queryset = GroupIndividual.objects.filter(group_id=group_id, role=GroupIndividual.Role.HEAD) - old_head = group_queryset.first() - if old_head: - old_head.role = GroupIndividual.Role.RECIPIENT - old_head.save(username=self.user.username) - - if group_queryset.exists(): - raise ValueError(_("more_than_one_head_in_group")) - - def _handle_group_update(self, obj_): - group = Group.objects.filter(groupindividual=obj_).first() - if group: - group.json_ext['head'] = f'{obj_.individual.first_name} {obj_.individual.last_name}' - group.save(username=self.user.username) + def _handle_head_change(self, obj_data, obj_): + with transaction.atomic(): + if obj_.role == GroupIndividual.Role.RECIPIENT and obj_data['role'] == GroupIndividual.Role.HEAD: + self._change_head(obj_data) + self._update_group_json_ext(obj_) + + def _change_head(self, obj_data): + with transaction.atomic(): + group_id = obj_data.get('group_id') + group_queryset = GroupIndividual.objects.filter(group_id=group_id, role=GroupIndividual.Role.HEAD) + old_head = group_queryset.first() + if old_head: + old_head.role = GroupIndividual.Role.RECIPIENT + old_head.save(username=self.user.username) + + if group_queryset.exists(): + raise ValueError(_("more_than_one_head_in_group")) + + def _update_group_json_ext(self, obj_): + with transaction.atomic(): + group = Group.objects.filter(groupindividual=obj_).first() + if group: + group.json_ext['head'] = f'{obj_.individual.first_name} {obj_.individual.last_name}' + group.save(username=self.user.username) @register_service_signal('group_individual.delete') def delete(self, obj_data): From f6000aa2ea79c39acc20a985673f1ab12088c29c Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 5 Jan 2024 09:46:41 +0100 Subject: [PATCH 4/9] CM-403: add task logic to GroupIndividual update (#38) * CM-404: update handle change head * CM-403: add task logic to GroupIndividual update --------- Co-authored-by: Jan --- individual/apps.py | 2 ++ individual/gql_mutations.py | 5 ++++- individual/services.py | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/individual/apps.py b/individual/apps.py index 2c5abbd..72072d4 100644 --- a/individual/apps.py +++ b/individual/apps.py @@ -10,6 +10,7 @@ "gql_group_update_perms": ["180003"], "gql_group_delete_perms": ["180004"], "gql_check_individual_update": True, + "gql_check_group_individual_update": True, } @@ -26,6 +27,7 @@ class IndividualConfig(AppConfig): gql_group_update_perms = None gql_group_delete_perms = None gql_check_individual_update = None + gql_check_group_individual_update = None def ready(self): from core.models import ModuleConfiguration diff --git a/individual/gql_mutations.py b/individual/gql_mutations.py index fd78cc7..6d1e3d9 100644 --- a/individual/gql_mutations.py +++ b/individual/gql_mutations.py @@ -261,7 +261,10 @@ def _mutate(cls, user, **data): data.pop('client_mutation_label') service = GroupIndividualService(user) - service.update(data) + if IndividualConfig.gql_check_group_individual_update: + service.create_update_task(data) + else: + service.update(data) class Input(UpdateGroupIndividualInputType): pass diff --git a/individual/services.py b/individual/services.py index 5f306e1..4b2933a 100644 --- a/individual/services.py +++ b/individual/services.py @@ -111,7 +111,7 @@ def update_group_individuals(self, obj_data): return output_exception(model_name=self.OBJECT_TYPE.__name__, method="update", exception=exc) -class GroupIndividualService(BaseService): +class GroupIndividualService(BaseService, UpdateCheckerLogicServiceMixin): OBJECT_TYPE = GroupIndividual def __init__(self, user, validation_class=GroupIndividualValidation): From b1700a8c56d2b84164907676c834477932e35c48 Mon Sep 17 00:00:00 2001 From: Jan Date: Mon, 8 Jan 2024 15:12:55 +0100 Subject: [PATCH 5/9] CM-403: adjust backend to handle individual and group tasks (#39) * CM-404: update handle change head * CM-403: add task logic to GroupIndividual update * CM-405: block more than one tasks --------- Co-authored-by: Jan --- individual/gql_mutations.py | 25 ++++++++++++++++--------- individual/signals/__init__.py | 22 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 individual/signals/__init__.py diff --git a/individual/gql_mutations.py b/individual/gql_mutations.py index 6d1e3d9..be9df81 100644 --- a/individual/gql_mutations.py +++ b/individual/gql_mutations.py @@ -65,7 +65,8 @@ def _mutate(cls, user, **data): data.pop('client_mutation_label') service = IndividualService(user) - service.create(data) + result = service.create(data) + return result if not result['success'] else None class Input(CreateIndividualInputType): pass @@ -93,9 +94,10 @@ def _mutate(cls, user, **data): service = IndividualService(user) if IndividualConfig.gql_check_individual_update: - service.create_update_task(data) + result = service.create_update_task(data) else: - service.update(data) + result = service.update(data) + return result if not result['success'] else None class Input(UpdateIndividualInputType): pass @@ -150,7 +152,8 @@ def _mutate(cls, user, **data): data.pop('client_mutation_label') service = GroupService(user) - service.create(data) + result = service.create(data) + return result if not result['success'] else None class Input(CreateGroupInputType): pass @@ -178,7 +181,8 @@ def _mutate(cls, user, **data): data.pop('client_mutation_label') service = GroupService(user) - service.update(data) + result = service.update(data) + return result if not result['success'] else None class Input(UpdateGroupInputType): pass @@ -233,7 +237,8 @@ def _mutate(cls, user, **data): data.pop('client_mutation_label') service = GroupIndividualService(user) - service.create(data) + result = service.create(data) + return result if not result['success'] else None class Input(CreateGroupIndividualInputType): pass @@ -262,9 +267,10 @@ def _mutate(cls, user, **data): service = GroupIndividualService(user) if IndividualConfig.gql_check_group_individual_update: - service.create_update_task(data) + result = service.create_update_task(data) else: - service.update(data) + result = service.update(data) + return result if not result['success'] else None class Input(UpdateGroupIndividualInputType): pass @@ -319,7 +325,8 @@ def _mutate(cls, user, **data): data.pop('client_mutation_label') service = GroupService(user) - service.create_group_individuals(data) + result = service.create_group_individuals(data) + return result if not result['success'] else None class Input(CreateGroupInputType): individual_ids = graphene.List(graphene.UUID) diff --git a/individual/signals/__init__.py b/individual/signals/__init__.py new file mode 100644 index 0000000..ef5418c --- /dev/null +++ b/individual/signals/__init__.py @@ -0,0 +1,22 @@ +import logging + +from core.service_signals import ServiceSignalBindType +from core.signals import bind_service_signal +from individual.services import GroupIndividualService, IndividualService + +from tasks_management.services import on_task_complete_service_handler + +logger = logging.getLogger(__name__) + + +def bind_service_signals(): + bind_service_signal( + 'task_service.complete_task', + on_task_complete_service_handler(GroupIndividualService), + bind_type=ServiceSignalBindType.AFTER + ) + bind_service_signal( + 'task_service.complete_task', + on_task_complete_service_handler(IndividualService), + bind_type=ServiceSignalBindType.AFTER + ) From 7bac319f6fa6966f4b00d4feef0fa775cb5e948d Mon Sep 17 00:00:00 2001 From: Jan Date: Mon, 8 Jan 2024 21:21:28 +0100 Subject: [PATCH 6/9] CM-405: save individual data in json_ext (#40) * CM-404: update handle change head * CM-403: add task logic to GroupIndividual update * CM-405: block more than one tasks * CM-405: save individual data in json_ext --------- Co-authored-by: Jan --- individual/services.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/individual/services.py b/individual/services.py index 4b2933a..b15d8c8 100644 --- a/individual/services.py +++ b/individual/services.py @@ -163,3 +163,10 @@ def _update_group_json_ext(self, obj_): @register_service_signal('group_individual.delete') def delete(self, obj_data): return super().delete(obj_data) + + def _data_for_json_ext_update(self, obj_data): + group_individual = GroupIndividual.objects.get(id=obj_data.get("id")) + individual = group_individual.individual + individual_identity_string = f'{individual.first_name} {individual.last_name}' + json_ext_data = {"individual_identity": individual_identity_string} + return json_ext_data From e88d6c20cc8ca35cbf8783cb873a33b144a6f1b1 Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 9 Jan 2024 12:16:47 +0100 Subject: [PATCH 7/9] CM-409: display members in change log (#41) * CM-404: update handle change head * CM-403: add task logic to GroupIndividual update * CM-405: block more than one tasks * CM-405: save individual data in json_ext * CM-409: display memebers --------- Co-authored-by: Jan --- individual/services.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/individual/services.py b/individual/services.py index b15d8c8..4482bb5 100644 --- a/individual/services.py +++ b/individual/services.py @@ -129,9 +129,12 @@ def update(self, obj_data): obj_data = self._adjust_update_payload(obj_data) self.validation_class.validate_update(self.user, **obj_data) obj_ = self.OBJECT_TYPE.objects.filter(id=obj_data['id']).first() + group_id_before_update = obj_.group.id self._handle_head_change(obj_data, obj_) [setattr(obj_, key, obj_data[key]) for key in obj_data] - return self.save_instance(obj_) + result = self.save_instance(obj_) + self._handle_members_change(group_id_before_update, obj_) + return result except Exception as exc: return output_exception(model_name=self.OBJECT_TYPE.__name__, method="update", exception=exc) @@ -141,6 +144,32 @@ def _handle_head_change(self, obj_data, obj_): self._change_head(obj_data) self._update_group_json_ext(obj_) + def _handle_members_change(self, group_id_before_update, obj_): + + with transaction.atomic(): + new_group = Group.objects.filter(id=group_id_before_update).first() + new_group_individuals = GroupIndividual.objects.filter(group_id=group_id_before_update) + + new_group_members = { + str(individual.individual.id): f"{individual.individual.first_name} {individual.individual.last_name}" + for individual in new_group_individuals + } + + new_group.json_ext["members"] = new_group_members + new_group.save(username=self.user.username) + + if group_id_before_update != obj_.group.id: + old_group = Group.objects.filter(id=obj_.group.id).first() + old_group_individuals = GroupIndividual.objects.filter(group_id=obj_.group.id) + + old_group_members = { + str(individual.individual.id): f"{individual.individual.first_name} {individual.individual.last_name}" + for individual in old_group_individuals + } + + old_group.json_ext["members"] = old_group_members + old_group.save(username=self.user.username) + def _change_head(self, obj_data): with transaction.atomic(): group_id = obj_data.get('group_id') From c7413500f32a0731c0d08757f1bae0d0f3fcf914 Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 10 Jan 2024 10:29:38 +0100 Subject: [PATCH 8/9] CM-407: adjust members and head changes to be reflected in the history (#43) * CM-407: adjust members and head changes to be reflected in the history * CM-407: change query --------- Co-authored-by: Jan --- individual/services.py | 62 +++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/individual/services.py b/individual/services.py index 4482bb5..8686c2e 100644 --- a/individual/services.py +++ b/individual/services.py @@ -133,7 +133,7 @@ def update(self, obj_data): self._handle_head_change(obj_data, obj_) [setattr(obj_, key, obj_data[key]) for key in obj_data] result = self.save_instance(obj_) - self._handle_members_change(group_id_before_update, obj_) + self._handle_json_ext(group_id_before_update, obj_) return result except Exception as exc: return output_exception(model_name=self.OBJECT_TYPE.__name__, method="update", exception=exc) @@ -142,33 +142,6 @@ def _handle_head_change(self, obj_data, obj_): with transaction.atomic(): if obj_.role == GroupIndividual.Role.RECIPIENT and obj_data['role'] == GroupIndividual.Role.HEAD: self._change_head(obj_data) - self._update_group_json_ext(obj_) - - def _handle_members_change(self, group_id_before_update, obj_): - - with transaction.atomic(): - new_group = Group.objects.filter(id=group_id_before_update).first() - new_group_individuals = GroupIndividual.objects.filter(group_id=group_id_before_update) - - new_group_members = { - str(individual.individual.id): f"{individual.individual.first_name} {individual.individual.last_name}" - for individual in new_group_individuals - } - - new_group.json_ext["members"] = new_group_members - new_group.save(username=self.user.username) - - if group_id_before_update != obj_.group.id: - old_group = Group.objects.filter(id=obj_.group.id).first() - old_group_individuals = GroupIndividual.objects.filter(group_id=obj_.group.id) - - old_group_members = { - str(individual.individual.id): f"{individual.individual.first_name} {individual.individual.last_name}" - for individual in old_group_individuals - } - - old_group.json_ext["members"] = old_group_members - old_group.save(username=self.user.username) def _change_head(self, obj_data): with transaction.atomic(): @@ -182,12 +155,33 @@ def _change_head(self, obj_data): if group_queryset.exists(): raise ValueError(_("more_than_one_head_in_group")) - def _update_group_json_ext(self, obj_): - with transaction.atomic(): - group = Group.objects.filter(groupindividual=obj_).first() - if group: - group.json_ext['head'] = f'{obj_.individual.first_name} {obj_.individual.last_name}' - group.save(username=self.user.username) + def _handle_json_ext(self, group_id_before_update, obj_): + self._update_json_ext_for_group(group_id_before_update) + if group_id_before_update != obj_.group.id: + self._update_json_ext_for_group(obj_.group.id) + + def _update_json_ext_for_group(self, group_id): + group = Group.objects.filter(id=group_id).first() + group_individuals = GroupIndividual.objects.filter(group_id=group_id) + head = group_individuals.filter(role=GroupIndividual.Role.HEAD).first() + + group_members = { + str(individual.individual.id): f"{individual.individual.first_name} {individual.individual.last_name}" + for individual in group_individuals + } + head_str = f'{head.individual.first_name} {head.individual.last_name}' if head else None + + changes_to_save = {} + + if group.json_ext.get("members") != group_members: + changes_to_save["members"] = group_members + + if group.json_ext.get("head") != head_str: + changes_to_save["head"] = head_str + + if changes_to_save: + group.json_ext.update(changes_to_save) + group.save(username=self.user.username) @register_service_signal('group_individual.delete') def delete(self, obj_data): From 51ac48104294ceb064ee5d640ad8270facc167d9 Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 10 Jan 2024 11:00:04 +0100 Subject: [PATCH 9/9] CM-407: improvements (#44) * CM-407: adjust members and head changes to be reflected in the history * CM-407: change query * CM-407: add user updated --------- Co-authored-by: Jan --- individual/gql_queries.py | 14 ++++++++------ individual/services.py | 6 +++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/individual/gql_queries.py b/individual/gql_queries.py index f84bd97..b89d53e 100644 --- a/individual/gql_queries.py +++ b/individual/gql_queries.py @@ -2,6 +2,7 @@ from graphene_django import DjangoObjectType from core import prefix_filterset, ExtendedConnection +from core.gql_queries import UserGQLType from individual.models import Individual, IndividualDataSource, Group, GroupIndividual, IndividualDataSourceUpload @@ -27,6 +28,10 @@ class Meta: class IndividualHistoryGQLType(DjangoObjectType): uuid = graphene.String(source='uuid') + user_updated = graphene.Field(UserGQLType) + + def resolve_user_updated(self, info): + return self.user_updated class Meta: model = Individual.history.model @@ -109,13 +114,10 @@ class Meta: class GroupHistoryGQLType(DjangoObjectType): uuid = graphene.String(source='uuid') - head = graphene.Field(IndividualGQLType) + user_updated = graphene.Field(UserGQLType) - def resolve_head(self, info): - return Individual.objects.filter( - groupindividual__group__id=self.id, - groupindividual__role=GroupIndividual.Role.HEAD - ).first() + def resolve_user_updated(self, info): + return self.user_updated class Meta: model = Group.history.model diff --git a/individual/services.py b/individual/services.py index 8686c2e..061478f 100644 --- a/individual/services.py +++ b/individual/services.py @@ -117,11 +117,11 @@ class GroupIndividualService(BaseService, UpdateCheckerLogicServiceMixin): def __init__(self, user, validation_class=GroupIndividualValidation): super().__init__(user, validation_class) - @register_service_signal('group_individual_service.create') + @register_service_signal('groupindividual_service.create') def create(self, obj_data): return super().create(obj_data) - @register_service_signal('group_individual.update') + @register_service_signal('groupindividual_service.update') @check_authentication def update(self, obj_data): try: @@ -183,7 +183,7 @@ def _update_json_ext_for_group(self, group_id): group.json_ext.update(changes_to_save) group.save(username=self.user.username) - @register_service_signal('group_individual.delete') + @register_service_signal('groupindividual_service.delete') def delete(self, obj_data): return super().delete(obj_data)