From 43e22ca9320261e23d4a46f1fbd94dcf57a98bde Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Mon, 25 Nov 2024 20:25:07 +0530 Subject: [PATCH] Remove event handlers (#2593) Remove event handlers (#2593) --- care/facility/api/serializers/daily_round.py | 20 +- .../api/serializers/encounter_symptom.py | 30 +- .../api/serializers/patient_consultation.py | 30 +- .../api/viewsets/consultation_diagnosis.py | 35 +- care/facility/api/viewsets/patient.py | 8 - care/facility/events/__init__.py | 0 care/facility/events/handler.py | 116 ------- .../management/commands/load_event_types.py | 302 ------------------ care/utils/event_utils.py | 54 ---- 9 files changed, 6 insertions(+), 589 deletions(-) delete mode 100644 care/facility/events/__init__.py delete mode 100644 care/facility/events/handler.py delete mode 100644 care/facility/management/commands/load_event_types.py diff --git a/care/facility/api/serializers/daily_round.py b/care/facility/api/serializers/daily_round.py index 768ddbabfd..bd8f105903 100644 --- a/care/facility/api/serializers/daily_round.py +++ b/care/facility/api/serializers/daily_round.py @@ -7,7 +7,6 @@ from rest_framework import serializers from rest_framework.exceptions import ValidationError -from care.facility.events.handler import create_consultation_events from care.facility.models import ( CATEGORY_CHOICES, COVID_CATEGORY_CHOICES, @@ -167,17 +166,7 @@ def update(self, instance, validated_data): facility=instance.consultation.patient.facility, ).generate() - instance = super().update(instance, validated_data) - - create_consultation_events( - instance.consultation_id, - instance, - instance.created_by_id, - instance.created_date, - fields_to_store=set(validated_data.keys()), - ) - - return instance + return super().update(instance, validated_data) def update_last_daily_round(self, daily_round_obj): consultation = daily_round_obj.consultation @@ -279,13 +268,6 @@ def create(self, validated_data): if daily_round_obj.rounds_type != DailyRound.RoundsType.AUTOMATED.value: self.update_last_daily_round(daily_round_obj) - create_consultation_events( - daily_round_obj.consultation_id, - daily_round_obj, - daily_round_obj.created_by_id, - daily_round_obj.created_date, - taken_at=daily_round_obj.taken_at, - ) return daily_round_obj def validate(self, attrs): diff --git a/care/facility/api/serializers/encounter_symptom.py b/care/facility/api/serializers/encounter_symptom.py index d669dd0aab..f0f04729e5 100644 --- a/care/facility/api/serializers/encounter_symptom.py +++ b/care/facility/api/serializers/encounter_symptom.py @@ -1,10 +1,6 @@ -from copy import copy - -from django.db import transaction from django.utils.timezone import now from rest_framework import serializers -from care.facility.events.handler import create_consultation_events from care.facility.models.encounter_symptom import ( ClinicalImpressionStatus, EncounterSymptom, @@ -90,34 +86,12 @@ def create(self, validated_data): validated_data["consultation"] = self.context["consultation"] validated_data["created_by"] = self.context["request"].user - with transaction.atomic(): - instance: EncounterSymptom = super().create(validated_data) - - create_consultation_events( - instance.consultation_id, - instance, - instance.created_by_id, - instance.created_date, - ) - - return instance + return super().create(validated_data) def update(self, instance, validated_data): validated_data["updated_by"] = self.context["request"].user - with transaction.atomic(): - old_instance = copy(instance) - instance = super().update(instance, validated_data) - - create_consultation_events( - instance.consultation_id, - instance, - instance.updated_by_id, - instance.modified_date, - old=old_instance, - ) - - return instance + return super().update(instance, validated_data) class EncounterCreateSymptomSerializer(serializers.ModelSerializer): diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index 25405b6f7e..ac2fdf5d00 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -1,4 +1,3 @@ -from copy import copy from datetime import timedelta from django.conf import settings @@ -24,7 +23,6 @@ EncounterSymptomSerializer, ) from care.facility.api.serializers.facility import FacilityBasicInfoSerializer -from care.facility.events.handler import create_consultation_events from care.facility.models import ( CATEGORY_CHOICES, COVID_CATEGORY_CHOICES, @@ -219,7 +217,6 @@ def validate_bed_number(self, bed_number): return bed_number def update(self, instance, validated_data): - old_instance = copy(instance) instance.last_edited_by = self.context["request"].user if instance.discharge_date: @@ -269,14 +266,6 @@ def update(self, instance, validated_data): consultation = super().update(instance, validated_data) - create_consultation_events( - consultation.id, - consultation, - self.context["request"].user.id, - consultation.modified_date, - old=old_instance, - ) - if ( "assigned_to" in validated_data and validated_data["assigned_to"] != _temp @@ -415,7 +404,7 @@ def create(self, validated_data): # noqa: PLR0915 PLR0912 ): consultation.is_readmission = True - diagnosis = ConsultationDiagnosis.objects.bulk_create( + ConsultationDiagnosis.objects.bulk_create( [ ConsultationDiagnosis( consultation=consultation, @@ -428,7 +417,7 @@ def create(self, validated_data): # noqa: PLR0915 PLR0912 ] ) - symptoms = EncounterSymptom.objects.bulk_create( + EncounterSymptom.objects.bulk_create( EncounterSymptom( consultation=consultation, symptom=obj.get("symptom"), @@ -469,13 +458,6 @@ def create(self, validated_data): # noqa: PLR0915 PLR0912 consultation.save() patient.save() - create_consultation_events( - consultation.id, - (consultation, *diagnosis, *symptoms), - consultation.created_by.id, - consultation.created_date, - ) - NotificationGenerator( event=Notification.Event.PATIENT_CONSULTATION_CREATED, caused_by=user, @@ -793,7 +775,6 @@ def validate(self, attrs): return attrs def update(self, instance: PatientConsultation, validated_data): - old_instance = copy(instance) with transaction.atomic(): instance = super().update(instance, validated_data) patient: PatientRegistration = instance.patient @@ -804,13 +785,6 @@ def update(self, instance: PatientConsultation, validated_data): ConsultationBed.objects.filter( consultation=self.instance, end_date__isnull=True ).update(end_date=now()) - create_consultation_events( - instance.id, - instance, - self.context["request"].user.id, - instance.modified_date, - old=old_instance, - ) return instance diff --git a/care/facility/api/viewsets/consultation_diagnosis.py b/care/facility/api/viewsets/consultation_diagnosis.py index cc76358be6..73d218bc3e 100644 --- a/care/facility/api/viewsets/consultation_diagnosis.py +++ b/care/facility/api/viewsets/consultation_diagnosis.py @@ -1,17 +1,13 @@ -from copy import copy - from django.shortcuts import get_object_or_404 from django_filters import rest_framework as filters from dry_rest_permissions.generics import DRYPermissions from rest_framework import mixins from rest_framework.permissions import IsAuthenticated -from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet from care.facility.api.serializers.consultation_diagnosis import ( ConsultationDiagnosisSerializer, ) -from care.facility.events.handler import create_consultation_events from care.facility.models import ( ConditionVerificationStatus, ConsultationDiagnosis, @@ -56,33 +52,4 @@ def get_queryset(self): def perform_create(self, serializer): consultation = self.get_consultation_obj() - diagnosis = serializer.save( - consultation=consultation, created_by=self.request.user - ) - create_consultation_events( - consultation.id, - diagnosis, - caused_by=self.request.user.id, - created_date=diagnosis.created_date, - ) - - def perform_update(self, serializer): - return serializer.save() - - def update(self, request, *args, **kwargs): - partial = kwargs.pop("partial", False) - instance = self.get_object() - old_instance = copy(instance) - serializer = self.get_serializer(instance, data=request.data, partial=partial) - serializer.is_valid(raise_exception=True) - instance = self.perform_update(serializer) - - create_consultation_events( - instance.consultation_id, - instance, - caused_by=self.request.user.id, - created_date=instance.created_date, - old=old_instance, - ) - - return Response(serializer.data) + serializer.save(consultation=consultation, created_by=self.request.user) diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index 585d13cdee..027a6c8241 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -49,7 +49,6 @@ ) from care.facility.api.serializers.patient_icmr import PatientICMRSerializer from care.facility.api.viewsets.mixins.history import HistoryMixin -from care.facility.events.handler import create_consultation_events from care.facility.models import ( CATEGORY_CHOICES, COVID_CATEGORY_CHOICES, @@ -982,13 +981,6 @@ def perform_create(self, serializer): created_by=self.request.user, ) - create_consultation_events( - instance.consultation_id, - instance, - self.request.user.id, - instance.created_date, - ) - message = { "facility_id": str(patient.facility.external_id), "patient_id": str(patient.external_id), diff --git a/care/facility/events/__init__.py b/care/facility/events/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/care/facility/events/handler.py b/care/facility/events/handler.py deleted file mode 100644 index a674c39e8e..0000000000 --- a/care/facility/events/handler.py +++ /dev/null @@ -1,116 +0,0 @@ -from contextlib import suppress -from datetime import datetime - -from django.core.exceptions import FieldDoesNotExist -from django.db import transaction -from django.db.models import Model -from django.db.models.query import QuerySet -from django.utils.timezone import now - -from care.facility.models.events import ChangeType, EventType, PatientConsultationEvent -from care.utils.event_utils import get_changed_fields, serialize_field - - -def create_consultation_event_entry( - consultation_id: int, - object_instance: Model, - caused_by: int, - created_date: datetime, - taken_at: datetime, - old_instance: Model | None = None, - fields_to_store: set[str] | None = None, -): - change_type = ChangeType.UPDATED if old_instance else ChangeType.CREATED - - fields: set[str] = ( - get_changed_fields(old_instance, object_instance) - if old_instance - else {field.name for field in object_instance._meta.fields} # noqa: SLF001 - ) - - fields_to_store = fields_to_store & fields if fields_to_store else fields - - batch = [] - groups = EventType.objects.filter( - model=object_instance.__class__.__name__, fields__len__gt=0, is_active=True - ).values_list("id", "fields") - for group_id, group_fields in groups: - if fields_to_store & {field.split("__", 1)[0] for field in group_fields}: - value = {} - for field in group_fields: - with suppress(FieldDoesNotExist): - value[field] = serialize_field(object_instance, field) - - if all(not v for v in value.values()): - continue - - PatientConsultationEvent.objects.select_for_update().filter( - consultation_id=consultation_id, - event_type=group_id, - is_latest=True, - object_model=object_instance.__class__.__name__, - object_id=object_instance.id, - taken_at__lt=taken_at, - ).update(is_latest=False) - batch.append( - PatientConsultationEvent( - consultation_id=consultation_id, - caused_by_id=caused_by, - event_type_id=group_id, - is_latest=True, - created_date=created_date, - taken_at=taken_at, - object_model=object_instance.__class__.__name__, - object_id=object_instance.id, - value=value, - change_type=change_type, - meta={ - "external_id": str(getattr(object_instance, "external_id", "")) - or None - }, - ) - ) - - PatientConsultationEvent.objects.bulk_create(batch) - return len(batch) - - -def create_consultation_events( - consultation_id: int, - objects: list | QuerySet | Model, - caused_by: int, - created_date: datetime | None = None, - taken_at: datetime | None = None, - old: Model | None = None, - fields_to_store: list[str] | set[str] | None = None, -): - if created_date is None: - created_date = now() - - if taken_at is None: - taken_at = created_date - - with transaction.atomic(): - if isinstance(objects, QuerySet | list | tuple): - if old is not None: - msg = "diff is not available when objects is a list or queryset" - raise ValueError(msg) - for obj in objects: - create_consultation_event_entry( - consultation_id, - obj, - caused_by, - created_date, - taken_at, - fields_to_store=set(fields_to_store) if fields_to_store else None, - ) - else: - create_consultation_event_entry( - consultation_id, - objects, - caused_by, - created_date, - taken_at, - old, - fields_to_store=set(fields_to_store) if fields_to_store else None, - ) diff --git a/care/facility/management/commands/load_event_types.py b/care/facility/management/commands/load_event_types.py deleted file mode 100644 index e0999e21aa..0000000000 --- a/care/facility/management/commands/load_event_types.py +++ /dev/null @@ -1,302 +0,0 @@ -from typing import TypedDict - -from django.core.management import BaseCommand - -from care.facility.models.events import EventType - - -class EventTypeDef(TypedDict, total=False): - name: str - model: str | None - children: tuple["EventType", ...] - fields: tuple[str, ...] - - -class Command(BaseCommand): - """ - Management command to load event types - """ - - consultation_event_types: tuple[EventTypeDef, ...] = ( - { - "name": "CONSULTATION", - "model": "PatientConsultation", - "children": ( - { - "name": "ENCOUNTER", - "children": ( - {"name": "PATIENT_NO", "fields": ("patient_no",)}, - {"name": "MEDICO_LEGAL_CASE", "fields": ("medico_legal_case",)}, - {"name": "ROUTE_TO_FACILITY", "fields": ("route_to_facility",)}, - ), - }, - { - "name": "CLINICAL", - "children": ( - { - "name": "DEATH", - "fields": ("death_datetime", "death_confirmed_doctor"), - }, - {"name": "SUGGESTION", "fields": ("suggestion",)}, - {"name": "CATEGORY", "fields": ("category",)}, - {"name": "EXAMINATION", "fields": ("examination_details",)}, - { - "name": "HISTORY_OF_PRESENT_ILLNESS", - "fields": ("history_of_present_illness",), - }, - {"name": "TREATMENT_PLAN", "fields": ("treatment_plan",)}, - { - "name": "CONSULTATION_NOTES", - "fields": ("consultation_notes",), - }, - { - "name": "COURSE_IN_FACILITY", - "fields": ("course_in_facility",), - }, - { - "name": "INVESTIGATION", - "fields": ("investigation",), - }, - { - "name": "TREATING_PHYSICIAN", - "fields": ( - "treating_physician__username", - "treating_physician__full_name", - ), - }, - ), - }, - { - "name": "HEALTH", - "children": ( - {"name": "HEIGHT", "fields": ("height",)}, - {"name": "WEIGHT", "fields": ("weight",)}, - ), - }, - { - "name": "INTERNAL_TRANSFER", - "children": ( - { - "name": "DISCHARGE", - "fields": ( - "discharge_date", - "discharge_reason", - "discharge_notes", - ), - }, - ), - }, - ), - }, - { - "name": "DAILY_ROUND", - "model": "DailyRound", - "children": ( - { - "name": "DAILY_ROUND_DETAILS", - "fields": ( - "taken_at", - "round_type", - "other_details", - "action", - "review_after", - ), - "children": ( - { - "name": "PHYSICAL_EXAMINATION", - "fields": ("physical_examination_info",), - }, - { - "name": "PATIENT_CATEGORY", - "fields": ("patient_category",), - }, - ), - }, - { - "name": "VITALS", - "children": ( - {"name": "TEMPERATURE", "fields": ("temperature",)}, - {"name": "PULSE", "fields": ("pulse",)}, - {"name": "BLOOD_PRESSURE", "fields": ("bp",)}, - {"name": "RESPIRATORY_RATE", "fields": ("resp",)}, - {"name": "RHYTHM", "fields": ("rhythm", "rhythm_detail")}, - {"name": "PAIN_SCALE", "fields": ("pain_scale_enhanced",)}, - ), - }, - { - "name": "NEUROLOGICAL", - "fields": ( - "left_pupil_size", - "left_pupil_size_detail", - "left_pupil_light_reaction", - "left_pupil_light_reaction_detail", - "right_pupil_size", - "right_pupil_size_detail", - "right_pupil_light_reaction", - "right_pupil_light_reaction_detail", - "glasgow_eye_open", - "glasgow_verbal_response", - "glasgow_motor_response", - "glasgow_total_calculated", - "limb_response_upper_extremity_left", - "limb_response_upper_extremity_right", - "limb_response_lower_extremity_left", - "limb_response_lower_extremity_right", - "consciousness_level", - "consciousness_level_detail", - "in_prone_position", - ), - }, - { - "name": "RESPIRATORY_SUPPORT", - "fields": ( - "bilateral_air_entry", - "etco2", - "ventilator_fio2", - "ventilator_interface", - "ventilator_mean_airway_pressure", - "ventilator_mode", - "ventilator_oxygen_modality", - "ventilator_oxygen_modality_flow_rate", - "ventilator_oxygen_modality_oxygen_rate", - "ventilator_peep", - "ventilator_pip", - "ventilator_pressure_support", - "ventilator_resp_rate", - "ventilator_spo2", - "ventilator_tidal_volume", - ), - }, - { - "name": "ARTERIAL_BLOOD_GAS_ANALYSIS", - "fields": ( - "base_excess", - "hco3", - "lactate", - "pco2", - "ph", - "po2", - "potassium", - "sodium", - ), - }, - { - "name": "BLOOD_GLUCOSE", - "fields": ( - "blood_sugar_level", - "insulin_intake_dose", - "insulin_intake_frequency", - ), - }, - { - "name": "IO_BALANCE", - "children": ( - {"name": "INFUSIONS", "fields": ("infusions",)}, - {"name": "IV_FLUIDS", "fields": ("iv_fluids",)}, - {"name": "FEEDS", "fields": ("feeds",)}, - {"name": "OUTPUT", "fields": ("output",)}, - { - "name": "TOTAL_INTAKE", - "fields": ("total_intake_calculated",), - }, - { - "name": "TOTAL_OUTPUT", - "fields": ("total_output_calculated",), - }, - ), - }, - { - "name": "DIALYSIS", - "fields": ( - "dialysis_fluid_balance", - "dialysis_net_balance", - ), - "children": ( - {"name": "PRESSURE_SORE", "fields": ("pressure_sore",)}, - ), - }, - {"name": "NURSING", "fields": ("nursing",)}, - { - "name": "ROUTINE", - "children": ( - {"name": "SLEEP_ROUTINE", "fields": ("sleep",)}, - {"name": "BOWEL_ROUTINE", "fields": ("bowel_issue",)}, - { - "name": "BLADDER_ROUTINE", - "fields": ( - "bladder_drainage", - "bladder_issue", - "experiences_dysuria", - "urination_frequency", - ), - }, - { - "name": "NUTRITION_ROUTINE", - "fields": ("nutrition_route", "oral_issue", "appetite"), - }, - ), - }, - ), - }, - { - "name": "PATIENT_NOTES", - "model": "PatientNotes", - "fields": ("note", "user_type"), - }, - { - "name": "DIAGNOSIS", - "model": "ConsultationDiagnosis", - "fields": ("diagnosis__label", "verification_status", "is_principal"), - }, - { - "name": "SYMPTOMS", - "model": "EncounterSymptom", - "fields": ( - "symptom", - "other_symptom", - "onset_date", - "cure_date", - "clinical_impression_status", - ), - }, - ) - - inactive_event_types: tuple[str, ...] = ( - "RESPIRATORY", - "INTAKE_OUTPUT", - "VENTILATOR_MODES", - "SYMPTOMS", - "ROUND_SYMPTOMS", - "SPO2", - ) - - def create_objects( - self, - types: tuple[EventType, ...], - model: str | None = None, - parent: EventType = None, - ): - for event_type in types: - model = event_type.get("model", model) - obj, _ = EventType.objects.update_or_create( - name=event_type["name"], - defaults={ - "parent": parent, - "model": model, - "fields": event_type.get("fields", []), - "is_active": True, - }, - ) - if children := event_type.get("children"): - self.create_objects(children, model, obj) - - def handle(self, *args, **options): - self.stdout.write("Loading Event Types... ", ending="") - - EventType.objects.filter(name__in=self.inactive_event_types).update( - is_active=False - ) - - self.create_objects(self.consultation_event_types) - - self.stdout.write(self.style.SUCCESS("OK")) diff --git a/care/utils/event_utils.py b/care/utils/event_utils.py index 55f1a183a5..316dfae8d8 100644 --- a/care/utils/event_utils.py +++ b/care/utils/event_utils.py @@ -2,63 +2,9 @@ from json import JSONEncoder from logging import getLogger -from django.core.exceptions import FieldDoesNotExist -from django.db.models import Field, Model - logger = getLogger(__name__) -def is_null(data): - return data is None or data == "" - - -def get_changed_fields(old: Model, new: Model) -> set[str]: - changed_fields: set[str] = set() - for field in new._meta.fields: # noqa: SLF001 - field_name = field.name - if getattr(old, field_name, None) != getattr(new, field_name, None): - changed_fields.add(field_name) - return changed_fields - - -def serialize_field(obj: Model, field_name: str): - if "__" in field_name: - field_name, sub_field = field_name.split("__", 1) - related_obj = getattr(obj, field_name, None) - return serialize_field(related_obj, sub_field) - - value = None - try: - value = getattr(obj, field_name) - except AttributeError: - if obj is not None: - logger.warning( - "Field %s not found in %s", field_name, obj.__class__.__name__ - ) - return None - - try: - # serialize choice fields with display value - field = obj._meta.get_field(field_name) # noqa: SLF001 - if issubclass(field.__class__, Field) and field.choices: - value = getattr(obj, f"get_{field_name}_display", lambda: value)() - except FieldDoesNotExist: - # the required field is a property and not a model field - pass - - return value - - -def model_diff(old, new): - diff = {} - for field in new._meta.fields: # noqa: SLF001 - field_name = field.name - if getattr(old, field_name, None) != getattr(new, field_name, None): - diff[field_name] = getattr(new, field_name, None) - - return diff - - class CustomJSONEncoder(JSONEncoder): def default(self, o): if isinstance(o, set):