this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Remove event handlers (#2593)

Remove event handlers (#2593)

authored by

Aakash Singh and committed by
GitHub
43e22ca9 d3d402db

+6 -589
+1 -19
care/facility/api/serializers/daily_round.py
··· 7 7 from rest_framework import serializers 8 8 from rest_framework.exceptions import ValidationError 9 9 10 - from care.facility.events.handler import create_consultation_events 11 10 from care.facility.models import ( 12 11 CATEGORY_CHOICES, 13 12 COVID_CATEGORY_CHOICES, ··· 167 166 facility=instance.consultation.patient.facility, 168 167 ).generate() 169 168 170 - instance = super().update(instance, validated_data) 171 - 172 - create_consultation_events( 173 - instance.consultation_id, 174 - instance, 175 - instance.created_by_id, 176 - instance.created_date, 177 - fields_to_store=set(validated_data.keys()), 178 - ) 179 - 180 - return instance 169 + return super().update(instance, validated_data) 181 170 182 171 def update_last_daily_round(self, daily_round_obj): 183 172 consultation = daily_round_obj.consultation ··· 279 268 if daily_round_obj.rounds_type != DailyRound.RoundsType.AUTOMATED.value: 280 269 self.update_last_daily_round(daily_round_obj) 281 270 282 - create_consultation_events( 283 - daily_round_obj.consultation_id, 284 - daily_round_obj, 285 - daily_round_obj.created_by_id, 286 - daily_round_obj.created_date, 287 - taken_at=daily_round_obj.taken_at, 288 - ) 289 271 return daily_round_obj 290 272 291 273 def validate(self, attrs):
+2 -28
care/facility/api/serializers/encounter_symptom.py
··· 1 - from copy import copy 2 - 3 - from django.db import transaction 4 1 from django.utils.timezone import now 5 2 from rest_framework import serializers 6 3 7 - from care.facility.events.handler import create_consultation_events 8 4 from care.facility.models.encounter_symptom import ( 9 5 ClinicalImpressionStatus, 10 6 EncounterSymptom, ··· 90 86 validated_data["consultation"] = self.context["consultation"] 91 87 validated_data["created_by"] = self.context["request"].user 92 88 93 - with transaction.atomic(): 94 - instance: EncounterSymptom = super().create(validated_data) 95 - 96 - create_consultation_events( 97 - instance.consultation_id, 98 - instance, 99 - instance.created_by_id, 100 - instance.created_date, 101 - ) 102 - 103 - return instance 89 + return super().create(validated_data) 104 90 105 91 def update(self, instance, validated_data): 106 92 validated_data["updated_by"] = self.context["request"].user 107 93 108 - with transaction.atomic(): 109 - old_instance = copy(instance) 110 - instance = super().update(instance, validated_data) 111 - 112 - create_consultation_events( 113 - instance.consultation_id, 114 - instance, 115 - instance.updated_by_id, 116 - instance.modified_date, 117 - old=old_instance, 118 - ) 119 - 120 - return instance 94 + return super().update(instance, validated_data) 121 95 122 96 123 97 class EncounterCreateSymptomSerializer(serializers.ModelSerializer):
+2 -28
care/facility/api/serializers/patient_consultation.py
··· 1 - from copy import copy 2 1 from datetime import timedelta 3 2 4 3 from django.conf import settings ··· 24 23 EncounterSymptomSerializer, 25 24 ) 26 25 from care.facility.api.serializers.facility import FacilityBasicInfoSerializer 27 - from care.facility.events.handler import create_consultation_events 28 26 from care.facility.models import ( 29 27 CATEGORY_CHOICES, 30 28 COVID_CATEGORY_CHOICES, ··· 219 217 return bed_number 220 218 221 219 def update(self, instance, validated_data): 222 - old_instance = copy(instance) 223 220 instance.last_edited_by = self.context["request"].user 224 221 225 222 if instance.discharge_date: ··· 269 266 270 267 consultation = super().update(instance, validated_data) 271 268 272 - create_consultation_events( 273 - consultation.id, 274 - consultation, 275 - self.context["request"].user.id, 276 - consultation.modified_date, 277 - old=old_instance, 278 - ) 279 - 280 269 if ( 281 270 "assigned_to" in validated_data 282 271 and validated_data["assigned_to"] != _temp ··· 415 404 ): 416 405 consultation.is_readmission = True 417 406 418 - diagnosis = ConsultationDiagnosis.objects.bulk_create( 407 + ConsultationDiagnosis.objects.bulk_create( 419 408 [ 420 409 ConsultationDiagnosis( 421 410 consultation=consultation, ··· 428 417 ] 429 418 ) 430 419 431 - symptoms = EncounterSymptom.objects.bulk_create( 420 + EncounterSymptom.objects.bulk_create( 432 421 EncounterSymptom( 433 422 consultation=consultation, 434 423 symptom=obj.get("symptom"), ··· 469 458 consultation.save() 470 459 patient.save() 471 460 472 - create_consultation_events( 473 - consultation.id, 474 - (consultation, *diagnosis, *symptoms), 475 - consultation.created_by.id, 476 - consultation.created_date, 477 - ) 478 - 479 461 NotificationGenerator( 480 462 event=Notification.Event.PATIENT_CONSULTATION_CREATED, 481 463 caused_by=user, ··· 793 775 return attrs 794 776 795 777 def update(self, instance: PatientConsultation, validated_data): 796 - old_instance = copy(instance) 797 778 with transaction.atomic(): 798 779 instance = super().update(instance, validated_data) 799 780 patient: PatientRegistration = instance.patient ··· 804 785 ConsultationBed.objects.filter( 805 786 consultation=self.instance, end_date__isnull=True 806 787 ).update(end_date=now()) 807 - create_consultation_events( 808 - instance.id, 809 - instance, 810 - self.context["request"].user.id, 811 - instance.modified_date, 812 - old=old_instance, 813 - ) 814 788 815 789 return instance 816 790
+1 -34
care/facility/api/viewsets/consultation_diagnosis.py
··· 1 - from copy import copy 2 - 3 1 from django.shortcuts import get_object_or_404 4 2 from django_filters import rest_framework as filters 5 3 from dry_rest_permissions.generics import DRYPermissions 6 4 from rest_framework import mixins 7 5 from rest_framework.permissions import IsAuthenticated 8 - from rest_framework.response import Response 9 6 from rest_framework.viewsets import GenericViewSet 10 7 11 8 from care.facility.api.serializers.consultation_diagnosis import ( 12 9 ConsultationDiagnosisSerializer, 13 10 ) 14 - from care.facility.events.handler import create_consultation_events 15 11 from care.facility.models import ( 16 12 ConditionVerificationStatus, 17 13 ConsultationDiagnosis, ··· 56 52 57 53 def perform_create(self, serializer): 58 54 consultation = self.get_consultation_obj() 59 - diagnosis = serializer.save( 60 - consultation=consultation, created_by=self.request.user 61 - ) 62 - create_consultation_events( 63 - consultation.id, 64 - diagnosis, 65 - caused_by=self.request.user.id, 66 - created_date=diagnosis.created_date, 67 - ) 68 - 69 - def perform_update(self, serializer): 70 - return serializer.save() 71 - 72 - def update(self, request, *args, **kwargs): 73 - partial = kwargs.pop("partial", False) 74 - instance = self.get_object() 75 - old_instance = copy(instance) 76 - serializer = self.get_serializer(instance, data=request.data, partial=partial) 77 - serializer.is_valid(raise_exception=True) 78 - instance = self.perform_update(serializer) 79 - 80 - create_consultation_events( 81 - instance.consultation_id, 82 - instance, 83 - caused_by=self.request.user.id, 84 - created_date=instance.created_date, 85 - old=old_instance, 86 - ) 87 - 88 - return Response(serializer.data) 55 + serializer.save(consultation=consultation, created_by=self.request.user)
-8
care/facility/api/viewsets/patient.py
··· 49 49 ) 50 50 from care.facility.api.serializers.patient_icmr import PatientICMRSerializer 51 51 from care.facility.api.viewsets.mixins.history import HistoryMixin 52 - from care.facility.events.handler import create_consultation_events 53 52 from care.facility.models import ( 54 53 CATEGORY_CHOICES, 55 54 COVID_CATEGORY_CHOICES, ··· 980 979 patient=patient, 981 980 consultation=patient.last_consultation, 982 981 created_by=self.request.user, 983 - ) 984 - 985 - create_consultation_events( 986 - instance.consultation_id, 987 - instance, 988 - self.request.user.id, 989 - instance.created_date, 990 982 ) 991 983 992 984 message = {
care/facility/events/__init__.py

This is a binary file and will not be displayed.

-116
care/facility/events/handler.py
··· 1 - from contextlib import suppress 2 - from datetime import datetime 3 - 4 - from django.core.exceptions import FieldDoesNotExist 5 - from django.db import transaction 6 - from django.db.models import Model 7 - from django.db.models.query import QuerySet 8 - from django.utils.timezone import now 9 - 10 - from care.facility.models.events import ChangeType, EventType, PatientConsultationEvent 11 - from care.utils.event_utils import get_changed_fields, serialize_field 12 - 13 - 14 - def create_consultation_event_entry( 15 - consultation_id: int, 16 - object_instance: Model, 17 - caused_by: int, 18 - created_date: datetime, 19 - taken_at: datetime, 20 - old_instance: Model | None = None, 21 - fields_to_store: set[str] | None = None, 22 - ): 23 - change_type = ChangeType.UPDATED if old_instance else ChangeType.CREATED 24 - 25 - fields: set[str] = ( 26 - get_changed_fields(old_instance, object_instance) 27 - if old_instance 28 - else {field.name for field in object_instance._meta.fields} # noqa: SLF001 29 - ) 30 - 31 - fields_to_store = fields_to_store & fields if fields_to_store else fields 32 - 33 - batch = [] 34 - groups = EventType.objects.filter( 35 - model=object_instance.__class__.__name__, fields__len__gt=0, is_active=True 36 - ).values_list("id", "fields") 37 - for group_id, group_fields in groups: 38 - if fields_to_store & {field.split("__", 1)[0] for field in group_fields}: 39 - value = {} 40 - for field in group_fields: 41 - with suppress(FieldDoesNotExist): 42 - value[field] = serialize_field(object_instance, field) 43 - 44 - if all(not v for v in value.values()): 45 - continue 46 - 47 - PatientConsultationEvent.objects.select_for_update().filter( 48 - consultation_id=consultation_id, 49 - event_type=group_id, 50 - is_latest=True, 51 - object_model=object_instance.__class__.__name__, 52 - object_id=object_instance.id, 53 - taken_at__lt=taken_at, 54 - ).update(is_latest=False) 55 - batch.append( 56 - PatientConsultationEvent( 57 - consultation_id=consultation_id, 58 - caused_by_id=caused_by, 59 - event_type_id=group_id, 60 - is_latest=True, 61 - created_date=created_date, 62 - taken_at=taken_at, 63 - object_model=object_instance.__class__.__name__, 64 - object_id=object_instance.id, 65 - value=value, 66 - change_type=change_type, 67 - meta={ 68 - "external_id": str(getattr(object_instance, "external_id", "")) 69 - or None 70 - }, 71 - ) 72 - ) 73 - 74 - PatientConsultationEvent.objects.bulk_create(batch) 75 - return len(batch) 76 - 77 - 78 - def create_consultation_events( 79 - consultation_id: int, 80 - objects: list | QuerySet | Model, 81 - caused_by: int, 82 - created_date: datetime | None = None, 83 - taken_at: datetime | None = None, 84 - old: Model | None = None, 85 - fields_to_store: list[str] | set[str] | None = None, 86 - ): 87 - if created_date is None: 88 - created_date = now() 89 - 90 - if taken_at is None: 91 - taken_at = created_date 92 - 93 - with transaction.atomic(): 94 - if isinstance(objects, QuerySet | list | tuple): 95 - if old is not None: 96 - msg = "diff is not available when objects is a list or queryset" 97 - raise ValueError(msg) 98 - for obj in objects: 99 - create_consultation_event_entry( 100 - consultation_id, 101 - obj, 102 - caused_by, 103 - created_date, 104 - taken_at, 105 - fields_to_store=set(fields_to_store) if fields_to_store else None, 106 - ) 107 - else: 108 - create_consultation_event_entry( 109 - consultation_id, 110 - objects, 111 - caused_by, 112 - created_date, 113 - taken_at, 114 - old, 115 - fields_to_store=set(fields_to_store) if fields_to_store else None, 116 - )
-302
care/facility/management/commands/load_event_types.py
··· 1 - from typing import TypedDict 2 - 3 - from django.core.management import BaseCommand 4 - 5 - from care.facility.models.events import EventType 6 - 7 - 8 - class EventTypeDef(TypedDict, total=False): 9 - name: str 10 - model: str | None 11 - children: tuple["EventType", ...] 12 - fields: tuple[str, ...] 13 - 14 - 15 - class Command(BaseCommand): 16 - """ 17 - Management command to load event types 18 - """ 19 - 20 - consultation_event_types: tuple[EventTypeDef, ...] = ( 21 - { 22 - "name": "CONSULTATION", 23 - "model": "PatientConsultation", 24 - "children": ( 25 - { 26 - "name": "ENCOUNTER", 27 - "children": ( 28 - {"name": "PATIENT_NO", "fields": ("patient_no",)}, 29 - {"name": "MEDICO_LEGAL_CASE", "fields": ("medico_legal_case",)}, 30 - {"name": "ROUTE_TO_FACILITY", "fields": ("route_to_facility",)}, 31 - ), 32 - }, 33 - { 34 - "name": "CLINICAL", 35 - "children": ( 36 - { 37 - "name": "DEATH", 38 - "fields": ("death_datetime", "death_confirmed_doctor"), 39 - }, 40 - {"name": "SUGGESTION", "fields": ("suggestion",)}, 41 - {"name": "CATEGORY", "fields": ("category",)}, 42 - {"name": "EXAMINATION", "fields": ("examination_details",)}, 43 - { 44 - "name": "HISTORY_OF_PRESENT_ILLNESS", 45 - "fields": ("history_of_present_illness",), 46 - }, 47 - {"name": "TREATMENT_PLAN", "fields": ("treatment_plan",)}, 48 - { 49 - "name": "CONSULTATION_NOTES", 50 - "fields": ("consultation_notes",), 51 - }, 52 - { 53 - "name": "COURSE_IN_FACILITY", 54 - "fields": ("course_in_facility",), 55 - }, 56 - { 57 - "name": "INVESTIGATION", 58 - "fields": ("investigation",), 59 - }, 60 - { 61 - "name": "TREATING_PHYSICIAN", 62 - "fields": ( 63 - "treating_physician__username", 64 - "treating_physician__full_name", 65 - ), 66 - }, 67 - ), 68 - }, 69 - { 70 - "name": "HEALTH", 71 - "children": ( 72 - {"name": "HEIGHT", "fields": ("height",)}, 73 - {"name": "WEIGHT", "fields": ("weight",)}, 74 - ), 75 - }, 76 - { 77 - "name": "INTERNAL_TRANSFER", 78 - "children": ( 79 - { 80 - "name": "DISCHARGE", 81 - "fields": ( 82 - "discharge_date", 83 - "discharge_reason", 84 - "discharge_notes", 85 - ), 86 - }, 87 - ), 88 - }, 89 - ), 90 - }, 91 - { 92 - "name": "DAILY_ROUND", 93 - "model": "DailyRound", 94 - "children": ( 95 - { 96 - "name": "DAILY_ROUND_DETAILS", 97 - "fields": ( 98 - "taken_at", 99 - "round_type", 100 - "other_details", 101 - "action", 102 - "review_after", 103 - ), 104 - "children": ( 105 - { 106 - "name": "PHYSICAL_EXAMINATION", 107 - "fields": ("physical_examination_info",), 108 - }, 109 - { 110 - "name": "PATIENT_CATEGORY", 111 - "fields": ("patient_category",), 112 - }, 113 - ), 114 - }, 115 - { 116 - "name": "VITALS", 117 - "children": ( 118 - {"name": "TEMPERATURE", "fields": ("temperature",)}, 119 - {"name": "PULSE", "fields": ("pulse",)}, 120 - {"name": "BLOOD_PRESSURE", "fields": ("bp",)}, 121 - {"name": "RESPIRATORY_RATE", "fields": ("resp",)}, 122 - {"name": "RHYTHM", "fields": ("rhythm", "rhythm_detail")}, 123 - {"name": "PAIN_SCALE", "fields": ("pain_scale_enhanced",)}, 124 - ), 125 - }, 126 - { 127 - "name": "NEUROLOGICAL", 128 - "fields": ( 129 - "left_pupil_size", 130 - "left_pupil_size_detail", 131 - "left_pupil_light_reaction", 132 - "left_pupil_light_reaction_detail", 133 - "right_pupil_size", 134 - "right_pupil_size_detail", 135 - "right_pupil_light_reaction", 136 - "right_pupil_light_reaction_detail", 137 - "glasgow_eye_open", 138 - "glasgow_verbal_response", 139 - "glasgow_motor_response", 140 - "glasgow_total_calculated", 141 - "limb_response_upper_extremity_left", 142 - "limb_response_upper_extremity_right", 143 - "limb_response_lower_extremity_left", 144 - "limb_response_lower_extremity_right", 145 - "consciousness_level", 146 - "consciousness_level_detail", 147 - "in_prone_position", 148 - ), 149 - }, 150 - { 151 - "name": "RESPIRATORY_SUPPORT", 152 - "fields": ( 153 - "bilateral_air_entry", 154 - "etco2", 155 - "ventilator_fio2", 156 - "ventilator_interface", 157 - "ventilator_mean_airway_pressure", 158 - "ventilator_mode", 159 - "ventilator_oxygen_modality", 160 - "ventilator_oxygen_modality_flow_rate", 161 - "ventilator_oxygen_modality_oxygen_rate", 162 - "ventilator_peep", 163 - "ventilator_pip", 164 - "ventilator_pressure_support", 165 - "ventilator_resp_rate", 166 - "ventilator_spo2", 167 - "ventilator_tidal_volume", 168 - ), 169 - }, 170 - { 171 - "name": "ARTERIAL_BLOOD_GAS_ANALYSIS", 172 - "fields": ( 173 - "base_excess", 174 - "hco3", 175 - "lactate", 176 - "pco2", 177 - "ph", 178 - "po2", 179 - "potassium", 180 - "sodium", 181 - ), 182 - }, 183 - { 184 - "name": "BLOOD_GLUCOSE", 185 - "fields": ( 186 - "blood_sugar_level", 187 - "insulin_intake_dose", 188 - "insulin_intake_frequency", 189 - ), 190 - }, 191 - { 192 - "name": "IO_BALANCE", 193 - "children": ( 194 - {"name": "INFUSIONS", "fields": ("infusions",)}, 195 - {"name": "IV_FLUIDS", "fields": ("iv_fluids",)}, 196 - {"name": "FEEDS", "fields": ("feeds",)}, 197 - {"name": "OUTPUT", "fields": ("output",)}, 198 - { 199 - "name": "TOTAL_INTAKE", 200 - "fields": ("total_intake_calculated",), 201 - }, 202 - { 203 - "name": "TOTAL_OUTPUT", 204 - "fields": ("total_output_calculated",), 205 - }, 206 - ), 207 - }, 208 - { 209 - "name": "DIALYSIS", 210 - "fields": ( 211 - "dialysis_fluid_balance", 212 - "dialysis_net_balance", 213 - ), 214 - "children": ( 215 - {"name": "PRESSURE_SORE", "fields": ("pressure_sore",)}, 216 - ), 217 - }, 218 - {"name": "NURSING", "fields": ("nursing",)}, 219 - { 220 - "name": "ROUTINE", 221 - "children": ( 222 - {"name": "SLEEP_ROUTINE", "fields": ("sleep",)}, 223 - {"name": "BOWEL_ROUTINE", "fields": ("bowel_issue",)}, 224 - { 225 - "name": "BLADDER_ROUTINE", 226 - "fields": ( 227 - "bladder_drainage", 228 - "bladder_issue", 229 - "experiences_dysuria", 230 - "urination_frequency", 231 - ), 232 - }, 233 - { 234 - "name": "NUTRITION_ROUTINE", 235 - "fields": ("nutrition_route", "oral_issue", "appetite"), 236 - }, 237 - ), 238 - }, 239 - ), 240 - }, 241 - { 242 - "name": "PATIENT_NOTES", 243 - "model": "PatientNotes", 244 - "fields": ("note", "user_type"), 245 - }, 246 - { 247 - "name": "DIAGNOSIS", 248 - "model": "ConsultationDiagnosis", 249 - "fields": ("diagnosis__label", "verification_status", "is_principal"), 250 - }, 251 - { 252 - "name": "SYMPTOMS", 253 - "model": "EncounterSymptom", 254 - "fields": ( 255 - "symptom", 256 - "other_symptom", 257 - "onset_date", 258 - "cure_date", 259 - "clinical_impression_status", 260 - ), 261 - }, 262 - ) 263 - 264 - inactive_event_types: tuple[str, ...] = ( 265 - "RESPIRATORY", 266 - "INTAKE_OUTPUT", 267 - "VENTILATOR_MODES", 268 - "SYMPTOMS", 269 - "ROUND_SYMPTOMS", 270 - "SPO2", 271 - ) 272 - 273 - def create_objects( 274 - self, 275 - types: tuple[EventType, ...], 276 - model: str | None = None, 277 - parent: EventType = None, 278 - ): 279 - for event_type in types: 280 - model = event_type.get("model", model) 281 - obj, _ = EventType.objects.update_or_create( 282 - name=event_type["name"], 283 - defaults={ 284 - "parent": parent, 285 - "model": model, 286 - "fields": event_type.get("fields", []), 287 - "is_active": True, 288 - }, 289 - ) 290 - if children := event_type.get("children"): 291 - self.create_objects(children, model, obj) 292 - 293 - def handle(self, *args, **options): 294 - self.stdout.write("Loading Event Types... ", ending="") 295 - 296 - EventType.objects.filter(name__in=self.inactive_event_types).update( 297 - is_active=False 298 - ) 299 - 300 - self.create_objects(self.consultation_event_types) 301 - 302 - self.stdout.write(self.style.SUCCESS("OK"))
-54
care/utils/event_utils.py
··· 2 2 from json import JSONEncoder 3 3 from logging import getLogger 4 4 5 - from django.core.exceptions import FieldDoesNotExist 6 - from django.db.models import Field, Model 7 - 8 5 logger = getLogger(__name__) 9 - 10 - 11 - def is_null(data): 12 - return data is None or data == "" 13 - 14 - 15 - def get_changed_fields(old: Model, new: Model) -> set[str]: 16 - changed_fields: set[str] = set() 17 - for field in new._meta.fields: # noqa: SLF001 18 - field_name = field.name 19 - if getattr(old, field_name, None) != getattr(new, field_name, None): 20 - changed_fields.add(field_name) 21 - return changed_fields 22 - 23 - 24 - def serialize_field(obj: Model, field_name: str): 25 - if "__" in field_name: 26 - field_name, sub_field = field_name.split("__", 1) 27 - related_obj = getattr(obj, field_name, None) 28 - return serialize_field(related_obj, sub_field) 29 - 30 - value = None 31 - try: 32 - value = getattr(obj, field_name) 33 - except AttributeError: 34 - if obj is not None: 35 - logger.warning( 36 - "Field %s not found in %s", field_name, obj.__class__.__name__ 37 - ) 38 - return None 39 - 40 - try: 41 - # serialize choice fields with display value 42 - field = obj._meta.get_field(field_name) # noqa: SLF001 43 - if issubclass(field.__class__, Field) and field.choices: 44 - value = getattr(obj, f"get_{field_name}_display", lambda: value)() 45 - except FieldDoesNotExist: 46 - # the required field is a property and not a model field 47 - pass 48 - 49 - return value 50 - 51 - 52 - def model_diff(old, new): 53 - diff = {} 54 - for field in new._meta.fields: # noqa: SLF001 55 - field_name = field.name 56 - if getattr(old, field_name, None) != getattr(new, field_name, None): 57 - diff[field_name] = getattr(new, field_name, None) 58 - 59 - return diff 60 6 61 7 62 8 class CustomJSONEncoder(JSONEncoder):