diff --git a/care/__init__.py b/care/__init__.py index e1d861523a..3fff62c9f9 100644 --- a/care/__init__.py +++ b/care/__init__.py @@ -3,5 +3,5 @@ [ int(num) if num.isdigit() else num for num in __version__.replace("-", ".", 1).split(".") - ] + ], ) diff --git a/care/abdm/api/serializers/__init__.py b/care/abdm/api/serializers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/care/abdm/api/serializers/auth.py b/care/abdm/api/serializers/auth.py index 9b533d0c9b..3863dd5ffd 100644 --- a/care/abdm/api/serializers/auth.py +++ b/care/abdm/api/serializers/auth.py @@ -1,3 +1,4 @@ +# ruff: noqa: N815 from rest_framework.serializers import CharField, IntegerField, Serializer diff --git a/care/abdm/api/serializers/healthid.py b/care/abdm/api/serializers/healthid.py index aa2b7cc1fd..20c874b143 100644 --- a/care/abdm/api/serializers/healthid.py +++ b/care/abdm/api/serializers/healthid.py @@ -1,3 +1,4 @@ +# ruff: noqa: N815 from rest_framework.serializers import CharField, Serializer, UUIDField diff --git a/care/abdm/api/serializers/hip.py b/care/abdm/api/serializers/hip.py index 4e3bb0f9ab..89e3486b4d 100644 --- a/care/abdm/api/serializers/hip.py +++ b/care/abdm/api/serializers/hip.py @@ -1,3 +1,4 @@ +# ruff: noqa: N815 from rest_framework.serializers import CharField, IntegerField, Serializer diff --git a/care/abdm/api/viewsets/__init__.py b/care/abdm/api/viewsets/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/care/abdm/api/viewsets/abha.py b/care/abdm/api/viewsets/abha.py index 7fcf102850..f8a8d20019 100644 --- a/care/abdm/api/viewsets/abha.py +++ b/care/abdm/api/viewsets/abha.py @@ -19,7 +19,7 @@ class AbhaViewSet(GenericViewSet): def get_abha_object(self): queryset = get_patient_queryset(self.request.user) patient_obj = get_object_or_404( - queryset.filter(external_id=self.kwargs.get("patient_external_id")) + queryset.filter(external_id=self.kwargs.get("patient_external_id")), ) return patient_obj.abha_number diff --git a/care/abdm/api/viewsets/auth.py b/care/abdm/api/viewsets/auth.py index ab7c88a1d4..eb4598d77f 100644 --- a/care/abdm/api/viewsets/auth.py +++ b/care/abdm/api/viewsets/auth.py @@ -1,7 +1,8 @@ import json -from datetime import datetime, timedelta +from datetime import timedelta from django.core.cache import cache +from django.utils import timezone from rest_framework import status from rest_framework.generics import GenericAPIView, get_object_or_404 from rest_framework.permissions import IsAuthenticated @@ -113,29 +114,28 @@ def post(self, request, *args, **kwargs): "No matching records found, need more data", status=status.HTTP_404_NOT_FOUND, ) - else: - for identifier in verified_identifiers: - if identifier["value"] is None: - continue - - # if identifier["type"] == "MOBILE": - # matched_by.append(identifier["value"]) - # mobile = identifier["value"].replace("+91", "").replace("-", "") - # patients = patients.filter( - # Q(phone_number=f"+91{mobile}") | Q(phone_number=mobile) - # ) - - if identifier["type"] == "NDHM_HEALTH_NUMBER": - matched_by.append(identifier["value"]) - patients = patients.filter( - abha_number__abha_number=identifier["value"] - ) - - if identifier["type"] == "HEALTH_ID": - matched_by.append(identifier["value"]) - patients = patients.filter( - abha_number__health_id=identifier["value"] - ) + for identifier in verified_identifiers: + if identifier["value"] is None: + continue + + # if identifier["type"] == "MOBILE": + # matched_by.append(identifier["value"]) + # mobile = identifier["value"].replace("+91", "").replace("-", "") + # patients = patients.filter( + # Q(phone_number=f"+91{mobile}") | Q(phone_number=mobile) + # ) + + if identifier["type"] == "NDHM_HEALTH_NUMBER": + matched_by.append(identifier["value"]) + patients = patients.filter( + abha_number__abha_number=identifier["value"], + ) + + if identifier["type"] == "HEALTH_ID": + matched_by.append(identifier["value"]) + patients = patients.filter( + abha_number__health_id=identifier["value"], + ) # TODO: also filter by demographics patient = patients.last() @@ -159,10 +159,10 @@ def post(self, request, *args, **kwargs): "name": f"Encounter: {str(consultation.created_date.date())}", }, PatientConsultation.objects.filter(patient=patient), - ) + ), ), "matched_by": matched_by, - } + }, ) return Response({}, status=status.HTTP_202_ACCEPTED) @@ -182,7 +182,7 @@ def post(self, request, *args, **kwargs): "transaction_id": data["transactionId"], "patient_id": data["patient"]["referenceNumber"], "phone": "7639899448", - } + }, ) return Response({}, status=status.HTTP_202_ACCEPTED) @@ -198,8 +198,8 @@ def post(self, request, *args, **kwargs): patient = get_object_or_404( PatientRegistration.objects.filter( - external_id=data["confirmation"]["linkRefNumber"] - ) + external_id=data["confirmation"]["linkRefNumber"], + ), ) AbdmGateway().on_link_confirm( { @@ -213,9 +213,9 @@ def post(self, request, *args, **kwargs): "name": f"Encounter: {str(consultation.created_date.date())}", }, PatientConsultation.objects.filter(patient=patient), - ) + ), ), - } + }, ) return Response({}, status=status.HTTP_202_ACCEPTED) @@ -234,7 +234,7 @@ def post(self, request, *args, **kwargs): { "request_id": data["requestId"], "consent_id": data["notification"]["consentId"], - } + }, ) return Response({}, status=status.HTTP_202_ACCEPTED) @@ -248,7 +248,7 @@ def post(self, request, *args, **kwargs): consent_id = data["hiRequest"]["consent"]["id"] consent = json.loads(cache.get(consent_id)) if consent_id in cache else None - if not consent or not consent["notification"]["status"] == "GRANTED": + if not consent or consent["notification"]["status"] != "GRANTED": return Response({}, status=status.HTTP_401_UNAUTHORIZED) # TODO: check if from and to are in range and consent expiry is greater than today @@ -263,13 +263,13 @@ def post(self, request, *args, **kwargs): # return Response({}, status=status.HTTP_403_FORBIDDEN) on_data_request_response = AbdmGateway().on_data_request( - {"request_id": data["requestId"], "transaction_id": data["transactionId"]} + {"request_id": data["requestId"], "transaction_id": data["transactionId"]}, ) - if not on_data_request_response.status_code == 202: - return Response({}, status=status.HTTP_202_ACCEPTED) + if on_data_request_response.status_code != status.HTTP_202_ACCEPTED: return Response( - on_data_request_response, status=status.HTTP_400_BAD_REQUEST + on_data_request_response, + status=status.HTTP_400_BAD_REQUEST, ) cipher = Cipher( @@ -296,18 +296,18 @@ def post(self, request, *args, **kwargs): PatientConsultation.objects.get( external_id=context[ "careContextReference" - ] - ) - ).create_record(record) + ], + ), + ).create_record(record), )["data"], }, consent["notification"]["consentDetail"]["hiTypes"], - ) + ), ), consent["notification"]["consentDetail"]["careContexts"][ :-2:-1 ], - ) + ), ), [], ), @@ -315,13 +315,13 @@ def post(self, request, *args, **kwargs): "cryptoAlg": "ECDH", "curve": "Curve25519", "dhPublicKey": { - "expiry": (datetime.now() + timedelta(days=2)).isoformat(), + "expiry": (timezone.now() + timedelta(days=2)).isoformat(), "parameters": "Curve25519/32byte random key", "keyValue": cipher.key_to_share, }, "nonce": cipher.sender_nonce, }, - } + }, ) AbdmGateway().data_notify( @@ -335,9 +335,9 @@ def post(self, request, *args, **kwargs): consent["notification"]["consentDetail"]["careContexts"][ :-2:-1 ], - ) + ), ), - } + }, ) return Response({}, status=status.HTTP_202_ACCEPTED) diff --git a/care/abdm/api/viewsets/health_facility.py b/care/abdm/api/viewsets/health_facility.py index 5fb41b5b4d..5367404da8 100644 --- a/care/abdm/api/viewsets/health_facility.py +++ b/care/abdm/api/viewsets/health_facility.py @@ -1,5 +1,6 @@ from celery import shared_task from dry_rest_permissions.generics import DRYPermissions +from rest_framework import status from rest_framework.decorators import action from rest_framework.mixins import ( CreateModelMixin, @@ -20,7 +21,7 @@ @shared_task def register_health_facility_as_service(facility_external_id): health_facility = HealthFacility.objects.filter( - facility__external_id=facility_external_id + facility__external_id=facility_external_id, ).first() if not health_facility: @@ -36,10 +37,10 @@ def register_health_facility_as_service(facility_external_id): "type": "HIP", "active": True, "alias": ["CARE_HIP"], - } + }, ) - if response.status_code == 200: + if response.status_code == status.HTTP_200_OK: health_facility.registered = True health_facility.save() return True diff --git a/care/abdm/api/viewsets/healthid.py b/care/abdm/api/viewsets/healthid.py index 1090c12d98..41c358d910 100644 --- a/care/abdm/api/viewsets/healthid.py +++ b/care/abdm/api/viewsets/healthid.py @@ -2,6 +2,7 @@ from datetime import datetime +from django.utils import timezone from drf_spectacular.utils import extend_schema from rest_framework import status from rest_framework.decorators import action @@ -27,8 +28,8 @@ from care.abdm.utils.api_call import AbdmGateway, HealthIdGateway from care.facility.api.serializers.patient import PatientDetailSerializer from care.facility.models.patient import PatientConsultation, PatientRegistration +from care.utils.exceptions import CaptchaRequiredException from care.utils.queryset.patient import get_patient_queryset -from config.auth_views import CaptchaRequiredException from config.ratelimit import ratelimit @@ -50,7 +51,10 @@ def generate_aadhaar_otp(self, request): if ratelimit(request, "generate_aadhaar_otp", [data["aadhaar"]]): raise CaptchaRequiredException( - detail={"status": 429, "detail": "Too Many Requests Provide Captcha"}, + detail={ + "status": status.HTTP_429_TOO_MANY_REQUESTS, + "detail": "Too Many Requests Provide Captcha", + }, code=status.HTTP_429_TOO_MANY_REQUESTS, ) @@ -72,7 +76,10 @@ def resend_aadhaar_otp(self, request): if ratelimit(request, "resend_aadhaar_otp", [data["txnId"]]): raise CaptchaRequiredException( - detail={"status": 429, "detail": "Too Many Requests Provide Captcha"}, + detail={ + "status": status.HTTP_429_TOO_MANY_REQUESTS, + "detail": "Too Many Requests Provide Captcha", + }, code=status.HTTP_429_TOO_MANY_REQUESTS, ) @@ -94,14 +101,17 @@ def verify_aadhaar_otp(self, request): if ratelimit(request, "verify_aadhaar_otp", [data["txnId"]]): raise CaptchaRequiredException( - detail={"status": 429, "detail": "Too Many Requests Provide Captcha"}, + detail={ + "status": status.HTTP_429_TOO_MANY_REQUESTS, + "detail": "Too Many Requests Provide Captcha", + }, code=status.HTTP_429_TOO_MANY_REQUESTS, ) serializer = VerifyOtpRequestPayloadSerializer(data=data) serializer.is_valid(raise_exception=True) response = HealthIdGateway().verify_aadhaar_otp( - data + data, ) # HealthIdGatewayV2().verify_document_mobile_otp(data) return Response(response, status=status.HTTP_200_OK) @@ -118,7 +128,10 @@ def generate_mobile_otp(self, request): if ratelimit(request, "generate_mobile_otp", [data["txnId"]]): raise CaptchaRequiredException( - detail={"status": 429, "detail": "Too Many Requests Provide Captcha"}, + detail={ + "status": status.HTTP_429_TOO_MANY_REQUESTS, + "detail": "Too Many Requests Provide Captcha", + }, code=status.HTTP_429_TOO_MANY_REQUESTS, ) @@ -140,7 +153,10 @@ def verify_mobile_otp(self, request): if ratelimit(request, "verify_mobile_otp", [data["txnId"]]): raise CaptchaRequiredException( - detail={"status": 429, "detail": "Too Many Requests Provide Captcha"}, + detail={ + "status": status.HTTP_429_TOO_MANY_REQUESTS, + "detail": "Too Many Requests Provide Captcha", + }, code=status.HTTP_429_TOO_MANY_REQUESTS, ) @@ -151,7 +167,7 @@ def verify_mobile_otp(self, request): def create_abha(self, abha_profile, token): abha_object = AbhaNumber.objects.filter( - abha_number=abha_profile["healthIdNumber"] + abha_number=abha_profile["healthIdNumber"], ).first() if abha_object: @@ -165,12 +181,7 @@ def create_abha(self, abha_profile, token): middle_name=abha_profile["middleName"], last_name=abha_profile["lastName"], gender=abha_profile["gender"], - date_of_birth=str( - datetime.strptime( - f"{abha_profile['yearOfBirth']}-{abha_profile['monthOfBirth']}-{abha_profile['dayOfBirth']}", - "%Y-%m-%d", - ) - )[0:10], + date_of_birth=f"{abha_profile['yearOfBirth']}-{abha_profile['monthOfBirth']}-{abha_profile['dayOfBirth']}", address=abha_profile["address"] if "address" in abha_profile else "", district=abha_profile["districtName"], state=abha_profile["stateName"], @@ -204,7 +215,10 @@ def create_health_id(self, request): if ratelimit(request, "create_health_id", [data["txnId"]]): raise CaptchaRequiredException( - detail={"status": 429, "detail": "Too Many Requests Provide Captcha"}, + detail={ + "status": status.HTTP_429_TOO_MANY_REQUESTS, + "detail": "Too Many Requests Provide Captcha", + }, code=status.HTTP_429_TOO_MANY_REQUESTS, ) @@ -263,10 +277,16 @@ def search_by_health_id(self, request): data = request.data if ratelimit( - request, "search_by_health_id", [data["healthId"]], increment=False + request, + "search_by_health_id", + [data["healthId"]], + increment=False, ): raise CaptchaRequiredException( - detail={"status": 429, "detail": "Too Many Requests Provide Captcha"}, + detail={ + "status": status.HTTP_429_TOO_MANY_REQUESTS, + "detail": "Too Many Requests Provide Captcha", + }, code=status.HTTP_429_TOO_MANY_REQUESTS, ) @@ -281,7 +301,10 @@ def get_abha_card(self, request): if ratelimit(request, "get_abha_card", [data["patient"]], increment=False): raise CaptchaRequiredException( - detail={"status": 429, "detail": "Too Many Requests Provide Captcha"}, + detail={ + "status": status.HTTP_429_TOO_MANY_REQUESTS, + "detail": "Too Many Requests Provide Captcha", + }, code=status.HTTP_429_TOO_MANY_REQUESTS, ) @@ -295,12 +318,12 @@ def get_abha_card(self, request): if data["type"] == "png": response = HealthIdGateway().get_abha_card_png( - {"refreshToken": patient.abha_number.refresh_token} + {"refreshToken": patient.abha_number.refresh_token}, ) return Response(response, status=status.HTTP_200_OK) response = HealthIdGateway().get_abha_card_pdf( - {"refreshToken": patient.abha_number.refresh_token} + {"refreshToken": patient.abha_number.refresh_token}, ) return Response(response, status=status.HTTP_200_OK) @@ -317,22 +340,32 @@ def link_via_qr(self, request): if ratelimit(request, "link_via_qr", [data["hidn"]], increment=False): raise CaptchaRequiredException( - detail={"status": 429, "detail": "Too Many Requests Provide Captcha"}, + detail={ + "status": status.HTTP_429_TOO_MANY_REQUESTS, + "detail": "Too Many Requests Provide Captcha", + }, code=status.HTTP_429_TOO_MANY_REQUESTS, ) serializer = QRContentSerializer(data=data) serializer.is_valid(raise_exception=True) - dob = datetime.strptime(data["dob"], "%d-%m-%Y").date() + dob = ( + datetime.strptime(data["dob"], "%d-%m-%Y") + .astimezone(timezone.get_default_timezone()) + .date() + ) patient = PatientRegistration.objects.filter( - abha_number__abha_number=data["hidn"] + abha_number__abha_number=data["hidn"], ).first() if patient: return Response( { - "message": "A patient is already associated with the provided Abha Number" + "message": ( + "A patient is already associated with " + "the provided Abha Number" + ), }, status=status.HTTP_400_BAD_REQUEST, ) @@ -358,15 +391,20 @@ def link_via_qr(self, request): "healthId": data["phr"] or data["hidn"], "name": data["name"], "gender": data["gender"], - "dateOfBirth": str(datetime.strptime(data["dob"], "%d-%m-%Y"))[ - 0:10 - ], - } + "dateOfBirth": str( + datetime.strptime( + data["dob"], + "%d-%m-%Y", + ) + .astimezone(timezone.get_default_timezone()) + .date(), + ), + }, ) if "patientId" in data and data["patientId"] is not None: patient = PatientRegistration.objects.filter( - external_id=data["patientId"] + external_id=data["patientId"], ).first() if not patient: @@ -395,12 +433,15 @@ def get_new_linking_token(self, request): if ratelimit(request, "get_new_linking_token", [data["patient"]]): raise CaptchaRequiredException( - detail={"status": 429, "detail": "Too Many Requests Provide Captcha"}, + detail={ + "status": status.HTTP_429_TOO_MANY_REQUESTS, + "detail": "Too Many Requests Provide Captcha", + }, code=status.HTTP_429_TOO_MANY_REQUESTS, ) patient = PatientDetailSerializer( - PatientRegistration.objects.get(external_id=data["patient"]) + PatientRegistration.objects.get(external_id=data["patient"]), ).data AbdmGateway().fetch_modes( @@ -409,7 +450,7 @@ def get_new_linking_token(self, request): "name": patient["abha_number_object"]["name"], "gender": patient["abha_number_object"]["gender"], "dateOfBirth": str(patient["abha_number_object"]["date_of_birth"]), - } + }, ) return Response({}, status=status.HTTP_200_OK) @@ -420,7 +461,10 @@ def add_care_context(self, request, *args, **kwargs): if ratelimit(request, "add_care_context", [consultation_id]): raise CaptchaRequiredException( - detail={"status": 429, "detail": "Too Many Requests Provide Captcha"}, + detail={ + "status": status.HTTP_429_TOO_MANY_REQUESTS, + "detail": "Too Many Requests Provide Captcha", + }, code=status.HTTP_429_TOO_MANY_REQUESTS, ) @@ -447,7 +491,7 @@ def add_care_context(self, request, *args, **kwargs): "consultationId": consultation_id, # "authMode": "DIRECT", "purpose": "LINK", - } + }, ) return Response(status=status.HTTP_202_ACCEPTED) @@ -458,7 +502,10 @@ def patient_sms_notify(self, request, *args, **kwargs): if ratelimit(request, "patient_sms_notify", [patient_id]): raise CaptchaRequiredException( - detail={"status": 429, "detail": "Too Many Requests Provide Captcha"}, + detail={ + "status": status.HTTP_429_TOO_MANY_REQUESTS, + "detail": "Too Many Requests Provide Captcha", + }, code=status.HTTP_429_TOO_MANY_REQUESTS, ) @@ -471,7 +518,7 @@ def patient_sms_notify(self, request, *args, **kwargs): ) response = AbdmGateway().patient_sms_notify( - {"phone": patient.phone_number, "healthId": patient.abha_number.health_id} + {"phone": patient.phone_number, "healthId": patient.abha_number.health_id}, ) return Response(response, status=status.HTTP_202_ACCEPTED) @@ -490,7 +537,10 @@ def auth_init(self, request): if ratelimit(request, "auth_init", [data["healthid"]]): raise CaptchaRequiredException( - detail={"status": 429, "detail": "Too Many Requests Provide Captcha"}, + detail={ + "status": status.HTTP_429_TOO_MANY_REQUESTS, + "detail": "Too Many Requests Provide Captcha", + }, code=status.HTTP_429_TOO_MANY_REQUESTS, ) @@ -512,7 +562,10 @@ def confirm_with_aadhaar_otp(self, request): if ratelimit(request, "confirm_with_aadhaar_otp", [data["txnId"]]): raise CaptchaRequiredException( - detail={"status": 429, "detail": "Too Many Requests Provide Captcha"}, + detail={ + "status": status.HTTP_429_TOO_MANY_REQUESTS, + "detail": "Too Many Requests Provide Captcha", + }, code=status.HTTP_429_TOO_MANY_REQUESTS, ) @@ -565,7 +618,10 @@ def confirm_with_mobile_otp(self, request): if ratelimit(request, "confirm_with_mobile_otp", [data["txnId"]]): raise CaptchaRequiredException( - detail={"status": 429, "detail": "Too Many Requests Provide Captcha"}, + detail={ + "status": status.HTTP_429_TOO_MANY_REQUESTS, + "detail": "Too Many Requests Provide Captcha", + }, code=status.HTTP_429_TOO_MANY_REQUESTS, ) @@ -617,7 +673,10 @@ def confirm_with_demographics(self, request): if ratelimit(request, "confirm_with_demographics", [data["txnId"]]): raise CaptchaRequiredException( - detail={"status": 429, "detail": "Too Many Requests Provide Captcha"}, + detail={ + "status": status.HTTP_429_TOO_MANY_REQUESTS, + "detail": "Too Many Requests Provide Captcha", + }, code=status.HTTP_429_TOO_MANY_REQUESTS, ) @@ -641,7 +700,10 @@ def check_and_generate_mobile_otp(self, request): if ratelimit(request, "check_and_generate_mobile_otp", [data["txnId"]]): raise CaptchaRequiredException( - detail={"status": 429, "detail": "Too Many Requests Provide Captcha"}, + detail={ + "status": status.HTTP_429_TOO_MANY_REQUESTS, + "detail": "Too Many Requests Provide Captcha", + }, code=status.HTTP_429_TOO_MANY_REQUESTS, ) diff --git a/care/abdm/api/viewsets/hip.py b/care/abdm/api/viewsets/hip.py index ac93ef17de..d2e65c1bb6 100644 --- a/care/abdm/api/viewsets/hip.py +++ b/care/abdm/api/viewsets/hip.py @@ -1,5 +1,5 @@ import uuid -from datetime import datetime, timezone +from datetime import UTC, datetime from rest_framework import status from rest_framework.decorators import action @@ -14,6 +14,8 @@ from care.facility.models.patient import PatientRegistration from config.authentication import ABDMAuthentication +HIP_CODE_LENGTH = 36 + class HipViewSet(GenericViewSet): permission_classes = (IsAuthenticated,) @@ -30,7 +32,7 @@ def share(self, request, *args, **kwargs): patient_data = data["profile"]["patient"] counter_id = ( data["profile"]["hipCode"] - if len(data["profile"]["hipCode"]) == 36 + if len(data["profile"]["hipCode"]) == HIP_CODE_LENGTH else Facility.objects.first().external_id ) @@ -49,7 +51,7 @@ def share(self, request, *args, **kwargs): patient_data["yearOfBirth"], ): patient = PatientRegistration.objects.filter( - abha_number__abha_number=patient_data["healthIdNumber"] + abha_number__abha_number=patient_data["healthIdNumber"], ).first() if not patient: @@ -60,7 +62,7 @@ def share(self, request, *args, **kwargs): is_antenatal=False, phone_number=patient_data["mobile"], emergency_phone_number=patient_data["mobile"], - date_of_birth=datetime.strptime( + date_of_birth=datetime.strptime( # noqa: DTZ007 f"{patient_data['yearOfBirth']}-{patient_data['monthOfBirth']}-{patient_data['dayOfBirth']}", "%Y-%m-%d", ).date(), @@ -75,12 +77,7 @@ def share(self, request, *args, **kwargs): health_id=patient_data["healthId"], name=patient_data["name"], gender=patient_data["gender"], - date_of_birth=str( - datetime.strptime( - f"{patient_data['yearOfBirth']}-{patient_data['monthOfBirth']}-{patient_data['dayOfBirth']}", - "%Y-%m-%d", - ) - )[0:10], + date_of_birth=f"{patient_data['yearOfBirth']}-{patient_data['monthOfBirth']}-{patient_data['dayOfBirth']}", address=patient_data["address"]["line"], district=patient_data["address"]["district"], state=patient_data["address"]["state"], @@ -97,19 +94,14 @@ def share(self, request, *args, **kwargs): or patient_data["healthIdNumber"], "name": patient_data["name"], "gender": patient_data["gender"], - "dateOfBirth": str( - datetime.strptime( - f"{patient_data['yearOfBirth']}-{patient_data['monthOfBirth']}-{patient_data['dayOfBirth']}", - "%Y-%m-%d", - ) - )[0:10], - } + "dateOfBirth": f"{patient_data['yearOfBirth']}-{patient_data['monthOfBirth']}-{patient_data['dayOfBirth']}", + }, ) payload = { "requestId": str(uuid.uuid4()), "timestamp": str( - datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z") + datetime.now(tz=UTC).strftime("%Y-%m-%dT%H:%M:%S.000Z"), ), "acknowledgement": { "status": "SUCCESS", @@ -124,7 +116,7 @@ def share(self, request, *args, **kwargs): } on_share_response = AbdmGateway().on_share(payload) - if on_share_response.status_code == 202: + if on_share_response.status_code == status.HTTP_202_ACCEPTED: return Response( on_share_response.request.body, status=status.HTTP_202_ACCEPTED, diff --git a/care/abdm/api/viewsets/monitoring.py b/care/abdm/api/viewsets/monitoring.py index 54cfe30069..3869f78fe9 100644 --- a/care/abdm/api/viewsets/monitoring.py +++ b/care/abdm/api/viewsets/monitoring.py @@ -1,4 +1,4 @@ -from datetime import datetime, timezone +from datetime import UTC, datetime from rest_framework import status from rest_framework.generics import GenericAPIView @@ -14,7 +14,7 @@ def get(self, request, *args, **kwargs): return Response( { "timestamp": str( - datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z") + datetime.now(tz=UTC).strftime("%Y-%m-%dT%H:%M:%S.000Z"), ), "status": "UP", "error": None, diff --git a/care/abdm/api/viewsets/status.py b/care/abdm/api/viewsets/status.py index 8c126ec7ef..dd41007e32 100644 --- a/care/abdm/api/viewsets/status.py +++ b/care/abdm/api/viewsets/status.py @@ -17,10 +17,10 @@ def post(self, request, *args, **kwargs): data = request.data PatientRegistration.objects.filter( - abha_number__health_id=data["notification"]["patient"]["id"] + abha_number__health_id=data["notification"]["patient"]["id"], ).update(abha_number=None) AbhaNumber.objects.filter( - health_id=data["notification"]["patient"]["id"] + health_id=data["notification"]["patient"]["id"], ).delete() AbdmGateway().patient_status_on_notify({"request_id": data["requestId"]}) diff --git a/care/abdm/models.py b/care/abdm/models.py index e02bfad1b6..e6ec2127c8 100644 --- a/care/abdm/models.py +++ b/care/abdm/models.py @@ -42,7 +42,9 @@ class HealthFacility(BaseModel, HealthFacilityPermissions): hf_id = models.CharField(max_length=50, unique=True) registered = models.BooleanField(default=False) facility = models.OneToOneField( - "facility.Facility", on_delete=models.PROTECT, to_field="external_id" + "facility.Facility", + on_delete=models.PROTECT, + to_field="external_id", ) def __str__(self): diff --git a/care/abdm/utils/__init__.py b/care/abdm/utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/care/abdm/utils/api_call.py b/care/abdm/utils/api_call.py index e955ab0142..e179cd7798 100644 --- a/care/abdm/utils/api_call.py +++ b/care/abdm/utils/api_call.py @@ -2,7 +2,7 @@ import logging import uuid from base64 import b64encode -from datetime import datetime, timedelta, timezone +from datetime import UTC, datetime, timedelta import requests from Crypto.Cipher import PKCS1_v1_5 @@ -10,16 +10,19 @@ from django.conf import settings from django.core.cache import cache from django.db.models import Q +from rest_framework import status from care.abdm.models import AbhaNumber from care.facility.models.patient_consultation import PatientConsultation +EXTERNAL_REQUEST_TIMEOUT = 25 + GATEWAY_API_URL = settings.ABDM_URL HEALTH_SERVICE_API_URL = settings.HEALTH_SERVICE_API_URL ABDM_DEVSERVICE_URL = GATEWAY_API_URL + "/devservice" ABDM_GATEWAY_URL = GATEWAY_API_URL + "/gateway" ABDM_TOKEN_URL = ABDM_GATEWAY_URL + "/v0.5/sessions" -ABDM_TOKEN_CACHE_KEY = "abdm_token" +ABDM_TOKEN_CACHE_KEY = "abdm_token" # noqa: S105 # TODO: Exception handling for all api calls, need to gracefully handle known exceptions @@ -28,7 +31,10 @@ def encrypt_with_public_key(a_message): rsa_public_key = RSA.importKey( - requests.get(HEALTH_SERVICE_API_URL + "/v2/auth/cert").text.strip() + requests.get( + HEALTH_SERVICE_API_URL + "/v2/auth/cert", + timeout=EXTERNAL_REQUEST_TIMEOUT, + ).text.strip(), ) rsa_public_key = PKCS1_v1_5.new(rsa_public_key) encrypted_text = rsa_public_key.encrypt(a_message.encode()) @@ -59,7 +65,7 @@ def add_user_header(self, headers, user_token): headers.update( { "X-Token": "Bearer " + user_token, - } + }, ) return headers @@ -76,32 +82,34 @@ def add_auth_header(self, headers): "Accept": "application/json", } resp = requests.post( - ABDM_TOKEN_URL, data=json.dumps(data), headers=auth_headers + ABDM_TOKEN_URL, + data=json.dumps(data), + headers=auth_headers, + timeout=EXTERNAL_REQUEST_TIMEOUT, ) - logger.info("Token Response Status: {}".format(resp.status_code)) - if resp.status_code < 300: + logger.info("Token Response Status: %s", resp.status_code) + if resp.status_code < status.HTTP_300_MULTIPLE_CHOICES: # Checking if Content-Type is application/json if resp.headers["Content-Type"] != "application/json": logger.info( - "Unsupported Content-Type: {}".format( - resp.headers["Content-Type"] - ) + "Unsupported Content-Type: %s", + resp.headers["Content-Type"], ) - logger.info("Response: {}".format(resp.text)) + logger.info("Response: %s", resp.text) return None - else: - data = resp.json() - token = data["accessToken"] - expires_in = data["expiresIn"] - logger.info("New Token: {}".format(token)) - logger.info("Expires in: {}".format(expires_in)) - cache.set(ABDM_TOKEN_CACHE_KEY, token, expires_in) + + data = resp.json() + token = data["accessToken"] + expires_in = data["expiresIn"] + logger.info("New Token: %s", token) + logger.info("Expires in: %s", expires_in) + cache.set(ABDM_TOKEN_CACHE_KEY, token, expires_in) else: - logger.info("Bad Response: {}".format(resp.text)) + logger.info("Bad Response: %s", resp.text) return None # logger.info("Returning Authorization Header: Bearer {}".format(token)) logger.info("Adding Authorization Header") - auth_header = {"Authorization": "Bearer {}".format(token)} + auth_header = {"Authorization": f"Bearer {token}"} return {**headers, **auth_header} def add_additional_headers(self, headers, additional_headers): @@ -113,9 +121,14 @@ def get(self, path, params=None, auth=None): headers = self.add_auth_header(headers) if auth: headers = self.add_user_header(headers, auth) - logger.info("Making GET Request to: {}".format(url)) - response = requests.get(url, headers=headers, params=params) - logger.info("{} Response: {}".format(response.status_code, response.text)) + logger.info("Making GET Request to: %s", url) + response = requests.get( + url, + headers=headers, + params=params, + timeout=EXTERNAL_REQUEST_TIMEOUT, + ) + logger.info("%s Response: %s", response.status_code, response.text) return response def post(self, path, data=None, auth=None, additional_headers=None, method="POST"): @@ -130,14 +143,16 @@ def post(self, path, data=None, auth=None, additional_headers=None, method="POST headers = self.add_user_header(headers, auth) if additional_headers: headers = self.add_additional_headers(headers, additional_headers) - # headers_string = " ".join( - # ['-H "{}: {}"'.format(k, v) for k, v in headers.items()] - # ) data_json = json.dumps(data) - # logger.info("curl -X POST {} {} -d {}".format(url, headers_string, data_json)) - logger.info("Posting Request to: {}".format(url)) - response = requests.request(method, url, headers=headers, data=data_json) - logger.info("{} Response: {}".format(response.status_code, response.text)) + logger.info("Posting Request to: %s", url) + response = requests.request( + method, + url, + headers=headers, + data=data_json, + timeout=EXTERNAL_REQUEST_TIMEOUT, + ) + logger.info("%s Response: %s", response.status_code, response.text) return response @@ -148,7 +163,7 @@ def __init__(self): def generate_aadhaar_otp(self, data): path = "/v1/registration/aadhaar/generateOtp" response = self.api.post(path, data) - logger.info("{} Response: {}".format(response.status_code, response.text)) + logger.info("%s Response: %s", response.status_code, response.text) return response.json() def resend_aadhaar_otp(self, data): @@ -180,7 +195,7 @@ def verify_mobile_otp(self, data): # /v1/registration/aadhaar/createHealthIdWithPreVerified def create_health_id(self, data): path = "/v1/registration/aadhaar/createHealthIdWithPreVerified" - logger.info("Creating Health ID with data: {}".format(data)) + logger.info("Creating Health ID with data: %s", data) # data.pop("healthId", None) response = self.api.post(path, data) return response.json() @@ -243,7 +258,7 @@ def confirm_with_demographics(self, data): def verify_demographics(self, health_id, name, gender, year_of_birth): auth_init_response = HealthIdGateway().auth_init( - {"authMethod": "DEMOGRAPHICS", "healthid": health_id} + {"authMethod": "DEMOGRAPHICS", "healthid": health_id}, ) if "txnId" in auth_init_response: demographics_response = HealthIdGateway().confirm_with_demographics( @@ -252,7 +267,7 @@ def verify_demographics(self, health_id, name, gender, year_of_birth): "name": name, "gender": gender, "yearOfBirth": year_of_birth, - } + }, ) return "status" in demographics_response and demographics_response["status"] @@ -262,19 +277,19 @@ def verify_demographics(self, health_id, name, gender, year_of_birth): def generate_access_token(self, data): if "access_token" in data: return data["access_token"] - elif "accessToken" in data: + if "accessToken" in data: return data["accessToken"] - elif "token" in data: + if "token" in data: return data["token"] if "refreshToken" in data: - refreshToken = data["refreshToken"] + refresh_token = data["refreshToken"] elif "refresh_token" in data: - refreshToken = data["refresh_token"] + refresh_token = data["refresh_token"] else: return None path = "/v1/auth/generate/access-token" - response = self.api.post(path, {"refreshToken": refreshToken}) + response = self.api.post(path, {"refreshToken": refresh_token}) return response.json()["accessToken"] # Account APIs @@ -305,9 +320,9 @@ def get_abha_card_pdf(self, data): def get_qr_code(self, data, auth): path = "/v1/account/qrCode" access_token = self.generate_access_token(data) - logger.info("Getting QR Code for: {}".format(data)) + logger.info("Getting QR Code for: %s", data) response = self.api.get(path, {}, access_token) - logger.info("QR Code Response: {}".format(response.text)) + logger.info("QR Code Response: %s", response.text) return response.json() @@ -354,38 +369,36 @@ def get_hip_id_by_health_id(self, health_id): def add_care_context(self, access_token, request_id): if request_id not in self.temp_memory: - return + return None data = self.temp_memory[request_id] if "consultationId" in data: consultation = PatientConsultation.objects.get( - external_id=data["consultationId"] + external_id=data["consultationId"], ) - response = self.add_contexts( + return self.add_contexts( { "access_token": access_token, "patient_id": str(consultation.patient.external_id), "patient_name": consultation.patient.name, "context_id": str(consultation.external_id), "context_name": f"Encounter: {str(consultation.created_date.date())}", - } + }, ) - return response - return False def save_linking_token(self, patient, access_token, request_id): if request_id not in self.temp_memory: - return + return None data = self.temp_memory[request_id] health_id = patient and patient["id"] or data["healthId"] abha_object = AbhaNumber.objects.filter( - Q(abha_number=health_id) | Q(health_id=health_id) + Q(abha_number=health_id) | Q(health_id=health_id), ).first() if abha_object: @@ -413,12 +426,12 @@ def fetch_modes(self, data): if "authMode" in data and data["authMode"] == "DIRECT": self.init(request_id) - return + return None payload = { "requestId": request_id, - "timestamp": datetime.now(tz=timezone.utc).strftime( - "%Y-%m-%dT%H:%M:%S.000Z" + "timestamp": datetime.now(tz=UTC).strftime( + "%Y-%m-%dT%H:%M:%S.000Z", ), "query": { "id": data["healthId"], @@ -429,13 +442,12 @@ def fetch_modes(self, data): }, }, } - response = self.api.post(path, payload, None, additional_headers) - return response + return self.api.post(path, payload, None, additional_headers) # "/v0.5/users/auth/init" def init(self, prev_request_id): if prev_request_id not in self.temp_memory: - return + return None path = "/v0.5/users/auth/init" additional_headers = {"X-CM-ID": settings.X_CM_ID} @@ -447,8 +459,8 @@ def init(self, prev_request_id): payload = { "requestId": request_id, - "timestamp": datetime.now(tz=timezone.utc).strftime( - "%Y-%m-%dT%H:%M:%S.000Z" + "timestamp": datetime.now(tz=UTC).strftime( + "%Y-%m-%dT%H:%M:%S.000Z", ), "query": { "id": data["healthId"], @@ -460,13 +472,12 @@ def init(self, prev_request_id): }, }, } - response = self.api.post(path, payload, None, additional_headers) - return response + return self.api.post(path, payload, None, additional_headers) # "/v0.5/users/auth/confirm" def confirm(self, transaction_id, prev_request_id): if prev_request_id not in self.temp_memory: - return + return None path = "/v0.5/users/auth/confirm" additional_headers = {"X-CM-ID": settings.X_CM_ID} @@ -478,8 +489,8 @@ def confirm(self, transaction_id, prev_request_id): payload = { "requestId": request_id, - "timestamp": datetime.now(tz=timezone.utc).strftime( - "%Y-%m-%dT%H:%M:%S.000Z" + "timestamp": datetime.now(tz=UTC).strftime( + "%Y-%m-%dT%H:%M:%S.000Z", ), "transactionId": transaction_id, "credential": { @@ -492,8 +503,7 @@ def confirm(self, transaction_id, prev_request_id): }, } - response = self.api.post(path, payload, None, additional_headers) - return response + return self.api.post(path, payload, None, additional_headers) def auth_on_notify(self, data): path = "/v0.5/links/link/on-init" @@ -502,16 +512,15 @@ def auth_on_notify(self, data): request_id = str(uuid.uuid4()) payload = { "requestId": request_id, - "timestamp": datetime.now(tz=timezone.utc).strftime( - "%Y-%m-%dT%H:%M:%S.000Z" + "timestamp": datetime.now(tz=UTC).strftime( + "%Y-%m-%dT%H:%M:%S.000Z", ), "acknowledgement": {"status": "OK"}, # "error": {"code": 1000, "message": "string"}, "resp": {"requestId": data["request_id"]}, } - response = self.api.post(path, payload, None, additional_headers) - return response + return self.api.post(path, payload, None, additional_headers) # TODO: make it dynamic and call it at discharge (call it from on_confirm) def add_contexts(self, data): @@ -522,8 +531,8 @@ def add_contexts(self, data): payload = { "requestId": request_id, - "timestamp": datetime.now(tz=timezone.utc).strftime( - "%Y-%m-%dT%H:%M:%S.000Z" + "timestamp": datetime.now(tz=UTC).strftime( + "%Y-%m-%dT%H:%M:%S.000Z", ), "link": { "accessToken": data["access_token"], @@ -534,14 +543,13 @@ def add_contexts(self, data): { "referenceNumber": data["context_id"], "display": data["context_name"], - } + }, ], }, }, } - response = self.api.post(path, payload, None, additional_headers) - return response + return self.api.post(path, payload, None, additional_headers) def on_discover(self, data): path = "/v0.5/care-contexts/on-discover" @@ -550,8 +558,8 @@ def on_discover(self, data): request_id = str(uuid.uuid4()) payload = { "requestId": request_id, - "timestamp": datetime.now(tz=timezone.utc).strftime( - "%Y-%m-%dT%H:%M:%S.000Z" + "timestamp": datetime.now(tz=UTC).strftime( + "%Y-%m-%dT%H:%M:%S.000Z", ), "transactionId": data["transaction_id"], "patient": { @@ -564,7 +572,7 @@ def on_discover(self, data): "display": context["name"], }, data["care_contexts"], - ) + ), ), "matchedBy": data["matched_by"], }, @@ -572,8 +580,7 @@ def on_discover(self, data): "resp": {"requestId": data["request_id"]}, } - response = self.api.post(path, payload, None, additional_headers) - return response + return self.api.post(path, payload, None, additional_headers) def on_link_init(self, data): path = "/v0.5/links/link/on-init" @@ -582,8 +589,8 @@ def on_link_init(self, data): request_id = str(uuid.uuid4()) payload = { "requestId": request_id, - "timestamp": datetime.now(tz=timezone.utc).strftime( - "%Y-%m-%dT%H:%M:%S.000Z" + "timestamp": datetime.now(tz=UTC).strftime( + "%Y-%m-%dT%H:%M:%S.000Z", ), "transactionId": data["transaction_id"], "link": { @@ -593,9 +600,9 @@ def on_link_init(self, data): "communicationMedium": "MOBILE", "communicationHint": data["phone"], "communicationExpiry": str( - (datetime.now() + timedelta(minutes=15)).strftime( - "%Y-%m-%dT%H:%M:%S.000Z" - ) + (datetime.now(tz=UTC) + timedelta(minutes=15)).strftime( + "%Y-%m-%dT%H:%M:%S.000Z", + ), ), }, }, @@ -603,8 +610,7 @@ def on_link_init(self, data): "resp": {"requestId": data["request_id"]}, } - response = self.api.post(path, payload, None, additional_headers) - return response + return self.api.post(path, payload, None, additional_headers) def on_link_confirm(self, data): path = "/v0.5/links/link/on-confirm" @@ -613,8 +619,8 @@ def on_link_confirm(self, data): request_id = str(uuid.uuid4()) payload = { "requestId": request_id, - "timestamp": datetime.now(tz=timezone.utc).strftime( - "%Y-%m-%dT%H:%M:%S.000Z" + "timestamp": datetime.now(tz=UTC).strftime( + "%Y-%m-%dT%H:%M:%S.000Z", ), "patient": { "referenceNumber": data["patient_id"], @@ -626,15 +632,14 @@ def on_link_confirm(self, data): "display": context["name"], }, data["care_contexts"], - ) + ), ), }, # "error": {"code": 1000, "message": "string"}, "resp": {"requestId": data["request_id"]}, } - response = self.api.post(path, payload, None, additional_headers) - return response + return self.api.post(path, payload, None, additional_headers) def on_notify(self, data): path = "/v0.5/consents/hip/on-notify" @@ -643,16 +648,15 @@ def on_notify(self, data): request_id = str(uuid.uuid4()) payload = { "requestId": request_id, - "timestamp": datetime.now(tz=timezone.utc).strftime( - "%Y-%m-%dT%H:%M:%S.000Z" + "timestamp": datetime.now(tz=UTC).strftime( + "%Y-%m-%dT%H:%M:%S.000Z", ), "acknowledgement": {"status": "OK", "consentId": data["consent_id"]}, # "error": {"code": 1000, "message": "string"}, "resp": {"requestId": data["request_id"]}, } - response = self.api.post(path, payload, None, additional_headers) - return response + return self.api.post(path, payload, None, additional_headers) def on_data_request(self, data): path = "/v0.5/health-information/hip/on-request" @@ -661,8 +665,8 @@ def on_data_request(self, data): request_id = str(uuid.uuid4()) payload = { "requestId": request_id, - "timestamp": datetime.now(tz=timezone.utc).strftime( - "%Y-%m-%dT%H:%M:%S.000Z" + "timestamp": datetime.now(tz=UTC).strftime( + "%Y-%m-%dT%H:%M:%S.000Z", ), "hiRequest": { "transactionId": data["transaction_id"], @@ -672,8 +676,7 @@ def on_data_request(self, data): "resp": {"requestId": data["request_id"]}, } - response = self.api.post(path, payload, None, additional_headers) - return response + return self.api.post(path, payload, None, additional_headers) def data_transfer(self, data): headers = {"Content-Type": "application/json"} @@ -691,15 +694,17 @@ def data_transfer(self, data): "careContextReference": context["consultation_id"], }, data["care_contexts"], - ) + ), ), "keyMaterial": data["key_material"], } - response = requests.post( - data["data_push_url"], data=json.dumps(payload), headers=headers + return requests.post( + data["data_push_url"], + data=json.dumps(payload), + headers=headers, + timeout=EXTERNAL_REQUEST_TIMEOUT, ) - return response def data_notify(self, data): path = "/v0.5/health-information/notify" @@ -708,14 +713,14 @@ def data_notify(self, data): request_id = str(uuid.uuid4()) payload = { "requestId": request_id, - "timestamp": datetime.now(tz=timezone.utc).strftime( - "%Y-%m-%dT%H:%M:%S.000Z" + "timestamp": datetime.now(tz=UTC).strftime( + "%Y-%m-%dT%H:%M:%S.000Z", ), "notification": { "consentId": data["consent_id"], "transactionId": data["transaction_id"], "doneAt": str( - datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z") + datetime.now(tz=UTC).strftime("%Y-%m-%dT%H:%M:%S.000Z"), ), "statusNotification": { "sessionStatus": "TRANSFERRED", @@ -728,14 +733,13 @@ def data_notify(self, data): "description": "success", # not sure what to put }, data["care_contexts"], - ) + ), ), }, }, } - response = self.api.post(path, payload, None, additional_headers) - return response + return self.api.post(path, payload, None, additional_headers) def patient_status_on_notify(self, data): path = "/v0.5/patients/status/on-notify" @@ -744,16 +748,15 @@ def patient_status_on_notify(self, data): request_id = str(uuid.uuid4()) payload = { "requestId": request_id, - "timestamp": datetime.now(tz=timezone.utc).strftime( - "%Y-%m-%dT%H:%M:%S.000Z" + "timestamp": datetime.now(tz=UTC).strftime( + "%Y-%m-%dT%H:%M:%S.000Z", ), "acknowledgement": {"status": "OK"}, # "error": {"code": 1000, "message": "string"}, "resp": {"requestId": data["request_id"]}, } - response = self.api.post(path, payload, None, additional_headers) - return response + return self.api.post(path, payload, None, additional_headers) def patient_sms_notify(self, data): path = "/v0.5/patients/sms/notify2" @@ -762,8 +765,8 @@ def patient_sms_notify(self, data): request_id = str(uuid.uuid4()) payload = { "requestId": request_id, - "timestamp": datetime.now(tz=timezone.utc).strftime( - "%Y-%m-%dT%H:%M:%S.000Z" + "timestamp": datetime.now(tz=UTC).strftime( + "%Y-%m-%dT%H:%M:%S.000Z", ), "notification": { "phoneNo": f"+91-{data['phone']}", @@ -771,15 +774,13 @@ def patient_sms_notify(self, data): }, } - response = self.api.post(path, payload, None, additional_headers) - return response + return self.api.post(path, payload, None, additional_headers) # /v1.0/patients/profile/on-share def on_share(self, data): path = "/v1.0/patients/profile/on-share" additional_headers = {"X-CM-ID": settings.X_CM_ID} - response = self.api.post(path, data, None, additional_headers) - return response + return self.api.post(path, data, None, additional_headers) class Bridge: @@ -788,5 +789,4 @@ def __init__(self): def add_update_service(self, data): path = "/v1/bridges/addUpdateServices" - response = self.api.post(path, data, method="PUT") - return response + return self.api.post(path, data, method="PUT") diff --git a/care/abdm/utils/cipher.py b/care/abdm/utils/cipher.py index 2401d1e1ed..45f215d714 100644 --- a/care/abdm/utils/cipher.py +++ b/care/abdm/utils/cipher.py @@ -2,6 +2,9 @@ import requests from django.conf import settings +from rest_framework import status + +FIDELIUS_API_TIMEOUT = 5 class Cipher: @@ -18,9 +21,12 @@ def __init__(self, reciever_public_key, reciever_nonce): self.key_to_share = None def generate_key_pair(self): - response = requests.get(f"{self.server_url}/keys/generate") + response = requests.get( + f"{self.server_url}/keys/generate", + timeout=FIDELIUS_API_TIMEOUT, + ) - if response.status_code == 200: + if response.status_code == status.HTTP_200_OK: key_material = response.json() self.sender_private_key = key_material["privateKey"] @@ -40,6 +46,7 @@ def encrypt(self, paylaod): response = requests.post( f"{self.server_url}/encrypt", + timeout=FIDELIUS_API_TIMEOUT, headers={"Content-Type": "application/json"}, data=json.dumps( { @@ -49,11 +56,11 @@ def encrypt(self, paylaod): "senderPublicKey": self.sender_public_key, "senderNonce": self.sender_nonce, "plainTextData": paylaod, - } + }, ), ) - if response.status_code == 200: + if response.status_code == status.HTTP_200_OK: data = response.json() self.key_to_share = data["keyToShare"] diff --git a/care/abdm/utils/fhir.py b/care/abdm/utils/fhir.py index 214257bc02..8c2e2bfc07 100644 --- a/care/abdm/utils/fhir.py +++ b/care/abdm/utils/fhir.py @@ -1,5 +1,5 @@ import base64 -from datetime import datetime, timezone +from datetime import UTC, datetime from uuid import uuid4 as uuid from fhir.resources.address import Address @@ -77,7 +77,11 @@ def _patient(self): id=id, identifier=[Identifier(value=id)], name=[HumanName(text=name)], - gender="male" if gender == 1 else "female" if gender == 2 else "other", + gender="male" + if gender == 1 + else "female" + if gender == 2 # noqa: PLR2004 + else "other", ) return self._patient_profile @@ -119,7 +123,7 @@ def _organization(self): self._organization_profile = Organization( id=id, identifier=[ - Identifier(system="https://facilitysbx.ndhm.gov.in", value=hip_id) + Identifier(system="https://facilitysbx.ndhm.gov.in", value=hip_id), ], name=name, telecom=[ContactPoint(system="phone", value=phone)], @@ -130,7 +134,7 @@ def _organization(self): state=state, postalCode=pincode, country="INDIA", - ) + ), ], ) @@ -149,10 +153,10 @@ def _condition(self, diagnosis_id, provisional=False): system="http://terminology.hl7.org/CodeSystem/condition-category", code="encounter-diagnosis", display="Encounter Diagnosis", - ) + ), ], text="Encounter Diagnosis", - ) + ), ], verificationStatus=CodeableConcept( coding=[ @@ -160,8 +164,8 @@ def _condition(self, diagnosis_id, provisional=False): system="http://terminology.hl7.org/CodeSystem/condition-ver-status", code="provisional" if provisional else "confirmed", display="Provisional" if provisional else "Confirmed", - ) - ] + ), + ], ), code=CodeableConcept( coding=[ @@ -169,7 +173,7 @@ def _condition(self, diagnosis_id, provisional=False): system="http://id.who.int/icd/release/11/mms", code=code, display=label, - ) + ), ], text=diagnosis.label, ), @@ -243,10 +247,10 @@ def _diagnostic_report(self): }, id=str(investigation.external_id), date=investigation.created_date.isoformat(), - ) + ), ), InvestigationValue.objects.filter(consultation=self.consultation), - ) + ), ), subject=self._reference(self._patient()), performer=[self._reference(self._organization())], @@ -257,8 +261,8 @@ def _diagnostic_report(self): return self._diagnostic_report_profile def _observation(self, title, value, id, date): - if not value or (type(value) == dict and not value["value"]): - return + if not value or (isinstance(value, dict) and not value["value"]): + return None return Observation( id=f"{id}.{title.replace(' ', '').replace('_', '-')}" @@ -268,24 +272,25 @@ def _observation(self, title, value, id, date): effectiveDateTime=date if date else None, code=CodeableConcept(text=title), valueQuantity=Quantity(value=str(value["value"]), unit=value["unit"]) - if type(value) == dict + if isinstance(value, dict) else None, - valueString=value if type(value) == str else None, + valueString=value if isinstance(value, str) else None, component=list( map( lambda component: ObservationComponent( code=CodeableConcept(text=component["title"]), valueQuantity=Quantity( - value=component["value"], unit=component["unit"] + value=component["value"], + unit=component["unit"], ) - if type(component) == dict + if isinstance(component, dict) else None, - valueString=component if type(component) == str else None, + valueString=component if isinstance(component, str) else None, ), value, - ) + ), ) - if type(value) == list + if isinstance(value, list) else None, ) @@ -341,7 +346,7 @@ def _observations_from_daily_round(self, daily_round): # TODO: do it for other fields like bp, pulse, spo2, ... observation_profiles = list( - filter(lambda profile: profile is not None, observation_profiles) + filter(lambda profile: profile is not None, observation_profiles), ) self._observation_profiles.extend(observation_profiles) return observation_profiles @@ -371,22 +376,22 @@ def _encounter(self, include_diagnosis=False): lambda diagnosis: EncounterDiagnosis( condition=self._reference( self._condition(diagnosis), - ) + ), ), self.consultation.icd11_diagnoses, - ) + ), ) + list( map( lambda diagnosis: EncounterDiagnosis( - condition=self._reference(self._condition(diagnosis)) + condition=self._reference(self._condition(diagnosis)), ), self.consultation.icd11_provisional_diagnoses, - ) + ), ) if include_diagnosis else None, - } + }, ) return self._encounter_profile @@ -396,7 +401,7 @@ def _immunization(self): return self._immunization_profile if not self.consultation.patient.is_vaccinated: - return + return None self._immunization_profile = Immunization( id=str(uuid()), @@ -405,7 +410,7 @@ def _immunization(self): Identifier( type=CodeableConcept(text="Covin Id"), value=self.consultation.patient.covin_id, - ) + ), ], vaccineCode=CodeableConcept( coding=[ @@ -413,7 +418,7 @@ def _immunization(self): system="http://snomed.info/sct", code="1119305005", display="COVID-19 antigen vaccine", - ) + ), ], text=self.consultation.patient.vaccine_name, ), @@ -424,16 +429,17 @@ def _immunization(self): system="https://projecteka.in/sct", code="47625008", display="Intravenous route", - ) - ] + ), + ], ), occurrenceDateTime=self.consultation.patient.last_vaccinated_date.isoformat(), protocolApplied=[ ImmunizationProtocolApplied( - doseNumberPositiveInt=self.consultation.patient.number_of_doses - ) + doseNumberPositiveInt=self.consultation.patient.number_of_doses, + ), ], ) + return None def _document_reference(self, file): id = str(file.external_id) @@ -446,9 +452,10 @@ def _document_reference(self, file): content=[ DocumentReferenceContent( attachment=Attachment( - contentType=content_type, data=base64.b64encode(content) - ) - ) + contentType=content_type, + data=base64.b64encode(content), + ), + ), ], author=[self._reference(self._organization())], ) @@ -498,11 +505,11 @@ def _prescription_composition(self): system="https://projecteka.in/sct", code="440545006", display="Prescription record", - ) - ] + ), + ], ), title="Prescription", - date=datetime.now(timezone.utc).isoformat(), + date=datetime.now(UTC).isoformat(), section=[ CompositionSection( title="In Patient Prescriptions", @@ -512,18 +519,18 @@ def _prescription_composition(self): system="https://projecteka.in/sct", code="440545006", display="Prescription record", - ) - ] + ), + ], ), entry=list( map( lambda medicine: self._reference( - self._medication_request(medicine)[1] + self._medication_request(medicine)[1], ), self.consultation.discharge_advice, - ) + ), ), - ) + ), ], subject=self._reference(self._patient()), encounter=self._reference(self._encounter()), @@ -542,11 +549,11 @@ def _health_document_composition(self): system="https://projecteka.in/sct", code="419891008", display="Record artifact", - ) - ] + ), + ], ), title="Health Document Record", - date=datetime.now(timezone.utc).isoformat(), + date=datetime.now(UTC).isoformat(), section=[ CompositionSection( title="Health Document Record", @@ -556,20 +563,20 @@ def _health_document_composition(self): system="https://projecteka.in/sct", code="419891008", display="Record artifact", - ) - ] + ), + ], ), entry=list( map( lambda file: self._reference( - self._document_reference(file) + self._document_reference(file), ), FileUpload.objects.filter( - associating_id=self.consultation.id + associating_id=self.consultation.id, ), - ) + ), ), - ) + ), ], subject=self._reference(self._patient()), encounter=self._reference(self._encounter()), @@ -587,11 +594,11 @@ def _wellness_composition(self): Coding( system="https://projecteka.in/sct", display="Wellness Record", - ) - ] + ), + ], ), title="Wellness Record", - date=datetime.now(timezone.utc).isoformat(), + date=datetime.now(UTC).isoformat(), section=list( map( lambda daily_round: CompositionSection( @@ -601,20 +608,20 @@ def _wellness_composition(self): Coding( system="https://projecteka.in/sct", display="Wellness Record", - ) - ] + ), + ], ), entry=list( map( lambda observation_profile: self._reference( - observation_profile + observation_profile, ), self._observations_from_daily_round(daily_round), - ) + ), ), ), self.consultation.daily_rounds.all(), - ) + ), ), subject=self._reference(self._patient()), encounter=self._reference(self._encounter()), @@ -637,7 +644,7 @@ def _immunization_composition(self): ], ), title="Immunization", - date=datetime.now(timezone.utc).isoformat(), + date=datetime.now(UTC).isoformat(), section=[ CompositionSection( title="IPD Immunization", @@ -655,12 +662,12 @@ def _immunization_composition(self): [self._reference(self._immunization())] if self._immunization() else [] - ) + ), ], emptyReason=None if self._immunization() else CodeableConcept( - coding=[Coding(code="notasked", display="Not Asked")] + coding=[Coding(code="notasked", display="Not Asked")], ), ), ], @@ -685,7 +692,7 @@ def _diagnostic_report_composition(self): ], ), title="Diagnostic Report", - date=datetime.now(timezone.utc).isoformat(), + date=datetime.now(UTC).isoformat(), section=[ CompositionSection( title="Investigation Report", @@ -718,11 +725,11 @@ def _discharge_summary_composition(self): system="https://projecteka.in/sct", code="373942005", display="Discharge Summary Record", - ) - ] + ), + ], ), title="Discharge Summary Document", - date=datetime.now(timezone.utc).isoformat(), + date=datetime.now(UTC).isoformat(), section=[ CompositionSection( title="Prescribed medications", @@ -732,16 +739,16 @@ def _discharge_summary_composition(self): system="https://projecteka.in/sct", code="440545006", display="Prescription", - ) - ] + ), + ], ), entry=list( map( lambda medicine: self._reference( - self._medication_request(medicine)[1] + self._medication_request(medicine)[1], ), self.consultation.discharge_advice, - ) + ), ), ), CompositionSection( @@ -752,18 +759,18 @@ def _discharge_summary_composition(self): system="https://projecteka.in/sct", code="419891008", display="Record", - ) - ] + ), + ], ), entry=list( map( lambda file: self._reference( - self._document_reference(file) + self._document_reference(file), ), FileUpload.objects.filter( - associating_id=self.consultation.id + associating_id=self.consultation.id, ), - ) + ), ), ), *list( @@ -775,20 +782,20 @@ def _discharge_summary_composition(self): Coding( system="https://projecteka.in/sct", display="Wellness Record", - ) - ] + ), + ], ), entry=list( map( lambda observation_profile: self._reference( - observation_profile + observation_profile, ), self._observations_from_daily_round(daily_round), - ) + ), ), ), self.consultation.daily_rounds.all(), - ) + ), ), CompositionSection( title="Procedures", @@ -798,16 +805,16 @@ def _discharge_summary_composition(self): system="https://projecteka.in/sct", code="371525003", display="Clinical procedure report", - ) - ] + ), + ], ), entry=list( map( lambda procedure: self._reference( - self._procedure(procedure) + self._procedure(procedure), ), self.consultation.procedure, - ) + ), ), ), CompositionSection( @@ -818,8 +825,8 @@ def _discharge_summary_composition(self): system="https://projecteka.in/sct", code="734163000", display="Care Plan", - ) - ] + ), + ], ), entry=[self._reference(self._careplan())], ), @@ -841,11 +848,11 @@ def _op_consultation_composition(self): system="https://projecteka.in/sct", code="371530004", display="Clinical consultation report", - ) - ] + ), + ], ), title="OP Consultation Document", - date=datetime.now(timezone.utc).isoformat(), + date=datetime.now(UTC).isoformat(), section=[ CompositionSection( title="Prescribed medications", @@ -855,16 +862,16 @@ def _op_consultation_composition(self): system="https://projecteka.in/sct", code="440545006", display="Prescription", - ) - ] + ), + ], ), entry=list( map( lambda medicine: self._reference( - self._medication_request(medicine)[1] + self._medication_request(medicine)[1], ), self.consultation.discharge_advice, - ) + ), ), ), CompositionSection( @@ -875,18 +882,18 @@ def _op_consultation_composition(self): system="https://projecteka.in/sct", code="419891008", display="Record", - ) - ] + ), + ], ), entry=list( map( lambda file: self._reference( - self._document_reference(file) + self._document_reference(file), ), FileUpload.objects.filter( - associating_id=self.consultation.id + associating_id=self.consultation.id, ), - ) + ), ), ), *list( @@ -898,20 +905,20 @@ def _op_consultation_composition(self): Coding( system="https://projecteka.in/sct", display="Wellness Record", - ) - ] + ), + ], ), entry=list( map( lambda observation_profile: self._reference( - observation_profile + observation_profile, ), self._observations_from_daily_round(daily_round), - ) + ), ), ), self.consultation.daily_rounds.all(), - ) + ), ), CompositionSection( title="Procedures", @@ -921,16 +928,16 @@ def _op_consultation_composition(self): system="https://projecteka.in/sct", code="371525003", display="Clinical procedure report", - ) - ] + ), + ], ), entry=list( map( lambda procedure: self._reference( - self._procedure(procedure) + self._procedure(procedure), ), self.consultation.procedure, - ) + ), ), ), CompositionSection( @@ -941,8 +948,8 @@ def _op_consultation_composition(self): system="https://projecteka.in/sct", code="734163000", display="Care Plan", - ) - ] + ), + ], ), entry=[self._reference(self._careplan())], ), @@ -957,7 +964,7 @@ def _bundle_entry(self, resource): def create_prescription_record(self): id = str(uuid()) - now = datetime.now(timezone.utc).isoformat() + now = datetime.now(UTC).isoformat() return Bundle( id=id, identifier=Identifier(value=id), @@ -974,20 +981,20 @@ def create_prescription_record(self): map( lambda resource: self._bundle_entry(resource), self._medication_profiles, - ) + ), ), *list( map( lambda resource: self._bundle_entry(resource), self._medication_request_profiles, - ) + ), ), ], ).json() def create_wellness_record(self): id = str(uuid()) - now = datetime.now(timezone.utc).isoformat() + now = datetime.now(UTC).isoformat() return Bundle( id=id, identifier=Identifier(value=id), @@ -1004,14 +1011,14 @@ def create_wellness_record(self): map( lambda resource: self._bundle_entry(resource), self._observation_profiles, - ) + ), ), ], ).json() def create_immunization_record(self): id = str(uuid()) - now = datetime.now(timezone.utc).isoformat() + now = datetime.now(UTC).isoformat() return Bundle( id=id, identifier=Identifier(value=id), @@ -1030,7 +1037,7 @@ def create_immunization_record(self): def create_diagnostic_report_record(self): id = str(uuid()) - now = datetime.now(timezone.utc).isoformat() + now = datetime.now(UTC).isoformat() return Bundle( id=id, identifier=Identifier(value=id), @@ -1047,14 +1054,14 @@ def create_diagnostic_report_record(self): map( lambda resource: self._bundle_entry(resource), self._observation_profiles, - ) + ), ), ], ).json() def create_health_document_record(self): id = str(uuid()) - now = datetime.now(timezone.utc).isoformat() + now = datetime.now(UTC).isoformat() return Bundle( id=id, identifier=Identifier(value=id), @@ -1071,14 +1078,14 @@ def create_health_document_record(self): map( lambda resource: self._bundle_entry(resource), self._document_reference_profiles, - ) + ), ), ], ).json() def create_discharge_summary_record(self): id = str(uuid()) - now = datetime.now(timezone.utc).isoformat() + now = datetime.now(UTC).isoformat() return Bundle( id=id, identifier=Identifier(value=id), @@ -1096,44 +1103,44 @@ def create_discharge_summary_record(self): map( lambda resource: self._bundle_entry(resource), self._medication_profiles, - ) + ), ), *list( map( lambda resource: self._bundle_entry(resource), self._medication_request_profiles, - ) + ), ), *list( map( lambda resource: self._bundle_entry(resource), self._condition_profiles, - ) + ), ), *list( map( lambda resource: self._bundle_entry(resource), self._procedure_profiles, - ) + ), ), *list( map( lambda resource: self._bundle_entry(resource), self._document_reference_profiles, - ) + ), ), *list( map( lambda resource: self._bundle_entry(resource), self._observation_profiles, - ) + ), ), ], ).json() def create_op_consultation_record(self): id = str(uuid()) - now = datetime.now(timezone.utc).isoformat() + now = datetime.now(UTC).isoformat() return Bundle( id=id, identifier=Identifier(value=id), @@ -1151,55 +1158,54 @@ def create_op_consultation_record(self): map( lambda resource: self._bundle_entry(resource), self._medication_profiles, - ) + ), ), *list( map( lambda resource: self._bundle_entry(resource), self._medication_request_profiles, - ) + ), ), *list( map( lambda resource: self._bundle_entry(resource), self._condition_profiles, - ) + ), ), *list( map( lambda resource: self._bundle_entry(resource), self._procedure_profiles, - ) + ), ), *list( map( lambda resource: self._bundle_entry(resource), self._document_reference_profiles, - ) + ), ), *list( map( lambda resource: self._bundle_entry(resource), self._observation_profiles, - ) + ), ), ], ).json() - def create_record(self, record_type): + def create_record(self, record_type): # noqa: PLR0911 if record_type == "Prescription": return self.create_prescription_record() - elif record_type == "WellnessRecord": + if record_type == "WellnessRecord": return self.create_wellness_record() - elif record_type == "ImmunizationRecord": + if record_type == "ImmunizationRecord": return self.create_immunization_record() - elif record_type == "HealthDocumentRecord": + if record_type == "HealthDocumentRecord": return self.create_health_document_record() - elif record_type == "DiagnosticReport": + if record_type == "DiagnosticReport": return self.create_diagnostic_report_record() - elif record_type == "DischargeSummary": + if record_type == "DischargeSummary": return self.create_discharge_summary_record() - elif record_type == "OPConsultation": + if record_type == "OPConsultation": return self.create_op_consultation_record() - else: - return self.create_discharge_summary_record() + return self.create_discharge_summary_record() diff --git a/care/audit_log/helpers.py b/care/audit_log/helpers.py index cd07097ba6..22ba0781ca 100644 --- a/care/audit_log/helpers.py +++ b/care/audit_log/helpers.py @@ -1,7 +1,7 @@ import re from fnmatch import fnmatch from functools import lru_cache -from typing import List, NamedTuple +from typing import NamedTuple from django.conf import settings from multiselectfield.db.fields import MSFList @@ -15,7 +15,7 @@ def remove_non_member_fields(d: dict): def instance_finder(v): return isinstance( v, - (list, dict, set, MSFList), + list | dict | set | MSFList, ) @@ -27,7 +27,7 @@ def seperate_hashable_dict(d: dict): def get_or_create_meta(instance): if not hasattr(instance._meta, "dal"): - setattr(instance._meta, "dal", MetaDataContainer()) + instance._meta.dal = MetaDataContainer() return instance @@ -35,19 +35,22 @@ def get_model_name(instance): return f"{instance._meta.app_label}.{instance.__class__.__name__}" -Search = NamedTuple("Search", [("type", str), ("value", str)]) +class Search(NamedTuple): + type: str # noqa: A003 + value: str def _make_search(item): splits = item.split(":") - if len(splits) == 2: + if len(splits) == 2: # noqa: PLR2004 return Search(type=splits[0], value=splits[1]) - else: - return Search(type="plain", value=splits[0]) + return Search(type="plain", value=splits[0]) def candidate_in_scope( - candidate: str, scope: List, is_application: bool = False + candidate: str, + scope: list, + is_application: bool = False, # noqa: FBT001 ) -> bool: """ Check if the candidate string is valid with the scope supplied, @@ -61,7 +64,7 @@ def candidate_in_scope( search_candidate = candidate if is_application: splits = candidate.split(".") - if len(splits) == 2: + if len(splits) == 2: # noqa: PLR2004 app_label, model_name = splits search_candidate = app_label @@ -81,7 +84,7 @@ def candidate_in_scope( return False -@lru_cache() +@lru_cache def exclude_model(model_name): if candidate_in_scope( model_name, @@ -91,7 +94,8 @@ def exclude_model(model_name): return True if candidate_in_scope( - model_name, settings.AUDIT_LOG["models"]["exclude"]["models"] + model_name, + settings.AUDIT_LOG["models"]["exclude"]["models"], ): return True diff --git a/care/audit_log/middleware.py b/care/audit_log/middleware.py index fdf7f8ffc5..baef669364 100644 --- a/care/audit_log/middleware.py +++ b/care/audit_log/middleware.py @@ -2,21 +2,19 @@ import threading import uuid from hashlib import md5 -from typing import NamedTuple, Optional +from typing import NamedTuple from django.conf import settings from django.contrib.auth.models import AnonymousUser from django.http import HttpRequest, HttpResponse -RequestInformation = NamedTuple( - "RequestInformation", - [ - ("request_id", str), - ("request", HttpRequest), - ("response", Optional[HttpResponse]), - ("exception", Optional[Exception]), - ], -) + +class RequestInformation(NamedTuple): + request_id: str + request: HttpRequest + response: HttpResponse | None + exception: Exception | None + logger = logging.getLogger(__name__) @@ -53,13 +51,16 @@ def save(request, response=None, exception=None): if not dal_request_id: dal_request_id = ( f"{request.method.lower()}::" - f"{md5(request.path.lower().encode('utf-8')).hexdigest()}::" + f"{md5(request.path.lower().encode('utf-8')).hexdigest()}::" # noqa: S324 f"{uuid.uuid4().hex}" ) - setattr(request, "dal_request_id", dal_request_id) + request.dal_request_id = dal_request_id AuditLogMiddleware.thread.__dal__ = RequestInformation( - dal_request_id, request, response, exception + dal_request_id, + request, + response, + exception, ) @staticmethod @@ -72,8 +73,7 @@ def get_current_user(): environ = RequestInformation(*AuditLogMiddleware.thread.__dal__) if isinstance(environ.request.user, AnonymousUser): return None - else: - return environ.request.user + return environ.request.user @staticmethod def get_current_request(): @@ -88,16 +88,16 @@ def __call__(self, request: HttpRequest): response: HttpResponse = self.get_response(request) self.save(request, response) - if request.user: - current_user_str = f"{request.user.id}|{request.user}" - else: - current_user_str = None + current_user_str = f"{request.user.id}|{request.user}" if request.user else None logger.info( - f"{request.method} {request.path} {response.status_code} " - f"User:[{current_user_str}]" + "%s %s %s User:[%s]", + request.method, + request.path, + response.status_code, + current_user_str, ) - return response + return (response,) def process_exception(self, request, exception): pass diff --git a/care/audit_log/receivers.py b/care/audit_log/receivers.py index d9b8c8abc5..50e34683d9 100644 --- a/care/audit_log/receivers.py +++ b/care/audit_log/receivers.py @@ -1,6 +1,6 @@ import json import logging -from typing import NamedTuple, Optional, Union +from typing import NamedTuple from django.conf import settings from django.contrib.auth.models import AbstractUser @@ -22,15 +22,12 @@ logger = logging.getLogger(__name__) -Event = NamedTuple( - "Event", - [ - ("model", str), - ("actor", AbstractUser), - ("entity_id", Union[int, str]), - ("changes", dict), - ], -) + +class Event(NamedTuple): + model: str + actor: AbstractUser + entity_id: int | str + changes: dict @receiver(pre_delete, weak=False) @@ -46,7 +43,7 @@ def pre_save_signal(sender, instance, **kwargs) -> None: model_name = get_model_name(instance) if exclude_model(model_name): - logger.debug(f"{model_name} ignored as per settings") + logger.debug("%s ignored as per settings", model_name) return get_or_create_meta(instance) @@ -66,7 +63,7 @@ def pre_save_signal(sender, instance, **kwargs) -> None: if operation not in {Operation.INSERT, Operation.DELETE}: old, new = remove_non_member_fields(pre.__dict__), remove_non_member_fields( - instance.__dict__ + instance.__dict__, ) try: @@ -81,11 +78,12 @@ def pre_save_signal(sender, instance, **kwargs) -> None: k: v for k, v in new_non_hashable.items() if v != old_non_hashable.get(k) - } + }, ) excluded_fields = settings.AUDIT_LOG["models"]["exclude"]["fields"].get( - model_name, [] + model_name, + [], ) for field in excluded_fields: if field in changes: @@ -105,13 +103,13 @@ def pre_save_signal(sender, instance, **kwargs) -> None: ) -def _post_processor(instance, event: Optional[Event], operation: Operation): +def _post_processor(instance, event: Event | None, operation: Operation): request_id = AuditLogMiddleware.get_current_request_id() actor = AuditLogMiddleware.get_current_user() model_name = get_model_name(instance) if not event and operation != Operation.DELETE: - logger.debug(f"Event not received for {operation}. Ignoring.") + logger.debug("Event not received for %s. Ignoring.", operation) return try: @@ -122,11 +120,17 @@ def _post_processor(instance, event: Optional[Event], operation: Operation): else: changes = json.dumps(event.changes if event else {}, cls=LogJsonEncoder) except Exception: - logger.warning(f"Failed to log {event}", exc_info=True) + logger.warning("Failed to log %s", event, exc_info=True) return logger.info( - f"AUDIT_LOG::{request_id}|{actor}|{operation.value}|{model_name}|ID:{instance.pk}|{changes}" + "AUDIT_LOG::%s|%s|%s|%s|ID:%s|%s", + request_id, + actor, + operation.value, + model_name, + instance.pk, + changes, ) @@ -141,7 +145,7 @@ def post_save_signal(sender, instance, created, update_fields: frozenset, **kwar model_name = get_model_name(instance) if exclude_model(model_name): - logger.debug(f"Ignoring {model_name}.") + logger.debug("Ignoring %s.", model_name) return operation = Operation.INSERT if created else Operation.UPDATE @@ -162,7 +166,7 @@ def post_delete_signal(sender, instance, **kwargs) -> None: model_name = get_model_name(instance) if exclude_model(model_name): - logger.debug(f"Ignoring {model_name}.") + logger.debug("Ignoring %s.", model_name) return event = instance._meta.dal.event diff --git a/care/facility/api/serializers/ambulance.py b/care/facility/api/serializers/ambulance.py index 1668922158..583da505b7 100644 --- a/care/facility/api/serializers/ambulance.py +++ b/care/facility/api/serializers/ambulance.py @@ -17,10 +17,12 @@ class AmbulanceSerializer(serializers.ModelSerializer): drivers = serializers.ListSerializer(child=AmbulanceDriverSerializer()) primary_district_object = DistrictSerializer( - read_only=True, source="primary_district" + read_only=True, + source="primary_district", ) secondary_district_object = DistrictSerializer( - read_only=True, source="secondary_district" + read_only=True, + source="secondary_district", ) third_district_object = DistrictSerializer(read_only=True, source="third_district") @@ -37,7 +39,7 @@ def validate(self, obj): validated = super().validate(obj) if not validated.get("price_per_km") and not validated.get("has_free_service"): raise ValidationError( - "The ambulance must provide a price or be marked as free" + "The ambulance must provide a price or be marked as free", ) return validated @@ -46,7 +48,7 @@ def create(self, validated_data): drivers = validated_data.pop("drivers", []) validated_data.pop("created_by", None) - ambulance = super(AmbulanceSerializer, self).create(validated_data) + ambulance = super().create(validated_data) for d in drivers: d["ambulance"] = ambulance @@ -55,8 +57,7 @@ def create(self, validated_data): def update(self, instance, validated_data): validated_data.pop("drivers", []) - ambulance = super(AmbulanceSerializer, self).update(instance, validated_data) - return ambulance + return super().update(instance, validated_data) class DeleteDriverSerializer(serializers.Serializer): diff --git a/care/facility/api/serializers/asset.py b/care/facility/api/serializers/asset.py index 57692a8519..423afc1a53 100644 --- a/care/facility/api/serializers/asset.py +++ b/care/facility/api/serializers/asset.py @@ -1,9 +1,7 @@ -from datetime import datetime - from django.core.cache import cache from django.db import transaction from django.shortcuts import get_object_or_404 -from django.utils.timezone import now +from django.utils import timezone from rest_framework import serializers from rest_framework.exceptions import ValidationError from rest_framework.serializers import ( @@ -52,8 +50,8 @@ def validate(self, data): ): raise ValidationError( { - "name": "Asset location with this name and facility already exists." - } + "name": "Asset location with this name and facility already exists.", + }, ) return data @@ -101,16 +99,14 @@ def update(self, instance, validated_data): with transaction.atomic(): edit = AssetServiceEdit( asset_service=instance, - edited_on=now(), + edited_on=timezone.now(), edited_by=user, serviced_on=serviced_on, note=note, ) edit.save() - updated_instance = super().update(instance, validated_data) - - return updated_instance + return super().update(instance, validated_data) class AssetSerializer(ModelSerializer): @@ -140,7 +136,7 @@ def validate(self, attrs): user = self.context["request"].user if "location" in attrs: location = get_object_or_404( - AssetLocation.objects.filter(external_id=attrs["location"]) + AssetLocation.objects.filter(external_id=attrs["location"]), ) facilities = get_facility_queryset(user) @@ -159,15 +155,17 @@ def validate(self, attrs): ): del attrs["warranty_amc_end_of_validity"] - elif warranty_amc_end_of_validity < datetime.now().date(): + elif warranty_amc_end_of_validity < timezone.now().date(): raise ValidationError( - "Warranty/AMC end of validity cannot be in the past" + "Warranty/AMC end of validity cannot be in the past", ) # validate that last serviced date is not in the future - if "last_serviced_on" in attrs and attrs["last_serviced_on"]: - if attrs["last_serviced_on"] > datetime.now().date(): - raise ValidationError("Last serviced on cannot be in the future") + if ( + attrs.get("last_serviced_on") + and attrs["last_serviced_on"] > timezone.now().date() + ): + raise ValidationError("Last serviced on cannot be in the future") # only allow setting asset class on creation (or updation if asset class is not set) if ( @@ -187,7 +185,9 @@ def create(self, validated_data): asset_instance = super().create(validated_data) if last_serviced_on or note: asset_service = AssetService( - asset=asset_instance, serviced_on=last_serviced_on, note=note + asset=asset_instance, + serviced_on=last_serviced_on, + note=note, ) asset_service.save() asset_instance.last_service = asset_service @@ -201,7 +201,8 @@ def update(self, instance, validated_data): not instance.last_service or instance.last_service.serviced_on != validated_data.get( - "last_serviced_on", instance.last_service.serviced_on + "last_serviced_on", + instance.last_service.serviced_on, ) or instance.last_service.note != validated_data.get("note", instance.last_service.note) @@ -213,7 +214,7 @@ def update(self, instance, validated_data): ) asset_service_initial_edit = AssetServiceEdit( asset_service=asset_service, - edited_on=now(), + edited_on=timezone.now(), edited_by=user, serviced_on=asset_service.serviced_on, note=asset_service.note, @@ -231,7 +232,7 @@ def update(self, instance, validated_data): != validated_data["current_location"].facility.id ): raise ValidationError( - {"location": "Interfacility transfer is not allowed here"} + {"location": "Interfacility transfer is not allowed here"}, ) AssetTransaction( from_location=instance.current_location, @@ -274,7 +275,7 @@ class Meta: class AssetActionSerializer(Serializer): - def actionChoices(): + def action_choices(): actions = [ OnvifAsset.OnvifActions, HL7MonitorAsset.HL7MonitorActions, @@ -285,8 +286,8 @@ def actionChoices(): choices += [(e.value, e.name) for e in action] return choices - type = ChoiceField( - choices=actionChoices(), + type = ChoiceField( # noqa: A003 + choices=action_choices(), required=True, ) data = JSONField(required=False) diff --git a/care/facility/api/serializers/bed.py b/care/facility/api/serializers/bed.py index 6624c3440f..ca950eecb1 100644 --- a/care/facility/api/serializers/bed.py +++ b/care/facility/api/serializers/bed.py @@ -31,6 +31,8 @@ from care.utils.serializer.external_id_field import ExternalIdSerializerField from config.serializers import ChoiceField +MAX_BULK_CREATE_BEDS = 100 + class BedSerializer(ModelSerializer): id = UUIDField(source="external_id", read_only=True) @@ -45,7 +47,7 @@ class BedSerializer(ModelSerializer): number_of_beds = IntegerField(required=False, default=1, write_only=True) def validate_number_of_beds(self, value): - if value > 100: + if value > MAX_BULK_CREATE_BEDS: raise ValidationError("Cannot create more than 100 beds at once.") return value @@ -58,10 +60,10 @@ def validate(self, attrs): user = self.context["request"].user if "location" in attrs and "facility" in attrs: location = get_object_or_404( - AssetLocation.objects.filter(external_id=attrs["location"]) + AssetLocation.objects.filter(external_id=attrs["location"]), ) facility = get_object_or_404( - Facility.objects.filter(external_id=attrs["facility"]) + Facility.objects.filter(external_id=attrs["facility"]), ) facilities = get_facility_queryset(user) if (not facilities.filter(id=location.facility.id).exists()) or ( @@ -73,7 +75,7 @@ def validate(self, attrs): attrs["facility"] = facility else: raise ValidationError( - {"location": "Field is Required", "facility": "Field is Required"} + {"location": "Field is Required", "facility": "Field is Required"}, ) return super().validate(attrs) @@ -96,7 +98,7 @@ def validate(self, attrs): user = self.context["request"].user if "asset" in attrs and "bed" in attrs: asset: Asset = get_object_or_404( - Asset.objects.filter(external_id=attrs["asset"]) + Asset.objects.filter(external_id=attrs["asset"]), ) bed: Bed = get_object_or_404(Bed.objects.filter(external_id=attrs["bed"])) facilities = get_facility_queryset(user) @@ -113,22 +115,23 @@ def validate(self, attrs): attrs["bed"] = bed if asset.current_location.facility.id != bed.facility.id: raise ValidationError( - {"asset": "Should be in the same facility as the bed"} + {"asset": "Should be in the same facility as the bed"}, ) if ( asset.asset_class == AssetClasses.HL7MONITOR.name and AssetBed.objects.filter( - bed=bed, asset__asset_class=asset.asset_class + bed=bed, + asset__asset_class=asset.asset_class, ).exists() ): raise ValidationError( { - "asset": "Bed is already in use by another asset of the same class" - } + "asset": "Bed is already in use by another asset of the same class", + }, ) else: raise ValidationError( - {"asset": "Field is Required", "bed": "Field is Required"} + {"asset": "Field is Required", "bed": "Field is Required"}, ) return super().validate(attrs) @@ -142,10 +145,11 @@ def get_patient(self, obj): from care.facility.api.serializers.patient import PatientListSerializer patient = PatientRegistration.objects.filter( - last_consultation__current_bed__bed=obj.bed + last_consultation__current_bed__bed=obj.bed, ).first() if patient: return PatientListSerializer(patient).data + return None class Meta: model = AssetBed @@ -158,10 +162,14 @@ class ConsultationBedSerializer(ModelSerializer): bed_object = BedSerializer(source="bed", read_only=True) consultation = ExternalIdSerializerField( - queryset=PatientConsultation.objects.all(), write_only=True, required=True + queryset=PatientConsultation.objects.all(), + write_only=True, + required=True, ) bed = ExternalIdSerializerField( - queryset=Bed.objects.all(), write_only=True, required=True + queryset=Bed.objects.all(), + write_only=True, + required=True, ) assets = ListField(child=UUIDField(), required=False, write_only=True) @@ -182,16 +190,16 @@ def validate(self, attrs): permitted_consultations = get_consultation_queryset(user) consultation: PatientConsultation = get_object_or_404( - permitted_consultations.filter(id=attrs["consultation"].id) + permitted_consultations.filter(id=attrs["consultation"].id), ) if not consultation.patient.is_active: raise ValidationError( - {"patient:": ["Patient is already discharged from CARE"]} + {"patient:": ["Patient is already discharged from CARE"]}, ) if consultation.facility_id != bed.facility_id: raise ValidationError( - {"consultation": "Should be in the same facility as the bed"} + {"consultation": "Should be in the same facility as the bed"}, ) previous_consultation_bed = consultation.current_bed @@ -200,13 +208,15 @@ def validate(self, attrs): and previous_consultation_bed.bed == bed and set( previous_consultation_bed.assets.order_by( - "external_id" - ).values_list("external_id", flat=True) + "external_id", + ).values_list("external_id", flat=True), ) == set(attrs.get("assets", [])) ): raise ValidationError( - {"consultation": "These set of bed and assets are already assigned"} + { + "consultation": "These set of bed and assets are already assigned", + }, ) start_date = attrs["start_date"] @@ -219,27 +229,30 @@ def validate(self, attrs): if start_date < latest_qs.start_date: raise ValidationError( { - "start_date": "Start date cannot be before the latest start date" - } + "start_date": "Start date cannot be before the latest start date", + }, ) if end_date and end_date < latest_qs.start_date: raise ValidationError( - {"end_date": "End date cannot be before the latest start date"} + {"end_date": "End date cannot be before the latest start date"}, ) # Conflict checking logic existing_qs = ConsultationBed.objects.filter(bed=bed).exclude( - consultation=consultation + consultation=consultation, ) if existing_qs.filter(start_date__gt=start_date).exists(): raise ValidationError({"start_date": "Cannot create conflicting entry"}) - if end_date: - if existing_qs.filter( - start_date__gt=end_date, end_date__lt=end_date - ).exists(): - raise ValidationError( - {"end_date": "Cannot create conflicting entry"} - ) + if ( + end_date + and existing_qs.filter( + start_date__gt=end_date, + end_date__lt=end_date, + ).exists() + ): + raise ValidationError( + {"end_date": "Cannot create conflicting entry"}, + ) else: raise ValidationError( @@ -247,7 +260,7 @@ def validate(self, attrs): "consultation": "Field is Required", "bed": "Field is Required", "start_date": "Field is Required", - } + }, ) return super().validate(attrs) @@ -256,7 +269,8 @@ def create(self, validated_data): with transaction.atomic(): ConsultationBed.objects.filter( - end_date__isnull=True, consultation=consultation + end_date__isnull=True, + consultation=consultation, ).update(end_date=validated_data["start_date"]) if assets_ids := validated_data.pop("assets", None): assets = ( @@ -266,8 +280,8 @@ def create(self, validated_data): Q(consultation_bed__end_date__gt=timezone.now()) | Q(consultation_bed__end_date__isnull=True), asset=OuterRef("pk"), - ) - ) + ), + ), ) .filter( is_in_use=False, @@ -278,25 +292,25 @@ def create(self, validated_data): asset_class__in=[ AssetClasses.HL7MONITOR.name, AssetClasses.ONVIF.name, - ] + ], ) .values_list("external_id", flat=True) ) not_found_assets = list(set(assets_ids) - set(assets)) if not_found_assets: raise ValidationError( - f"Some assets are not available - {' ,'.join(map(str, not_found_assets))}" + f"Some assets are not available - {' ,'.join(map(str, not_found_assets))}", ) obj: ConsultationBed = super().create(validated_data) if assets_ids: asset_objects = Asset.objects.filter(external_id__in=assets_ids).only( - "id" + "id", ) ConsultationBedAsset.objects.bulk_create( [ ConsultationBedAsset(consultation_bed=obj, asset=asset) for asset in asset_objects - ] + ], ) consultation.current_bed = obj diff --git a/care/facility/api/serializers/daily_round.py b/care/facility/api/serializers/daily_round.py index 340962d86d..560fe1a029 100644 --- a/care/facility/api/serializers/daily_round.py +++ b/care/facility/api/serializers/daily_round.py @@ -28,19 +28,24 @@ class DailyRoundSerializer(serializers.ModelSerializer): id = serializers.CharField(source="external_id", read_only=True) additional_symptoms = serializers.MultipleChoiceField( - choices=SYMPTOM_CHOICES, required=False + choices=SYMPTOM_CHOICES, + required=False, ) deprecated_covid_category = ChoiceField( - choices=COVID_CATEGORY_CHOICES, required=False + choices=COVID_CATEGORY_CHOICES, + required=False, ) # Deprecated patient_category = ChoiceField(choices=CATEGORY_CHOICES, required=False) current_health = ChoiceField(choices=CURRENT_HEALTH_CHOICES, required=False) action = ChoiceField( - choices=PatientRegistration.ActionChoices, write_only=True, required=False + choices=PatientRegistration.ActionChoices, + write_only=True, + required=False, ) review_interval = serializers.IntegerField( - source="consultation__review_interval", required=False + source="consultation__review_interval", + required=False, ) taken_at = serializers.DateTimeField(required=True) @@ -50,42 +55,55 @@ class DailyRoundSerializer(serializers.ModelSerializer): # Critical Care Components consciousness_level = ChoiceField( - choices=DailyRound.ConsciousnessChoice, required=False + choices=DailyRound.ConsciousnessChoice, + required=False, ) left_pupil_light_reaction = ChoiceField( - choices=DailyRound.PupilReactionChoice, required=False + choices=DailyRound.PupilReactionChoice, + required=False, ) right_pupil_light_reaction = ChoiceField( - choices=DailyRound.PupilReactionChoice, required=False + choices=DailyRound.PupilReactionChoice, + required=False, ) limb_response_upper_extremity_right = ChoiceField( - choices=DailyRound.LimbResponseChoice, required=False + choices=DailyRound.LimbResponseChoice, + required=False, ) limb_response_upper_extremity_left = ChoiceField( - choices=DailyRound.LimbResponseChoice, required=False + choices=DailyRound.LimbResponseChoice, + required=False, ) limb_response_lower_extremity_left = ChoiceField( - choices=DailyRound.LimbResponseChoice, required=False + choices=DailyRound.LimbResponseChoice, + required=False, ) limb_response_lower_extremity_right = ChoiceField( - choices=DailyRound.LimbResponseChoice, required=False + choices=DailyRound.LimbResponseChoice, + required=False, ) rhythm = ChoiceField(choices=DailyRound.RythmnChoice, required=False) ventilator_interface = ChoiceField( - choices=DailyRound.VentilatorInterfaceChoice, required=False + choices=DailyRound.VentilatorInterfaceChoice, + required=False, ) ventilator_mode = ChoiceField( - choices=DailyRound.VentilatorModeChoice, required=False + choices=DailyRound.VentilatorModeChoice, + required=False, ) ventilator_oxygen_modality = ChoiceField( - choices=DailyRound.VentilatorOxygenModalityChoice, required=False + choices=DailyRound.VentilatorOxygenModalityChoice, + required=False, ) insulin_intake_frequency = ChoiceField( - choices=DailyRound.InsulinIntakeFrequencyChoice, required=False + choices=DailyRound.InsulinIntakeFrequencyChoice, + required=False, ) clone_last = serializers.BooleanField( - write_only=True, default=False, required=False + write_only=True, + default=False, + required=False, ) last_edited_by = UserBaseMinimumSerializer(read_only=True) @@ -109,7 +127,7 @@ def update(self, instance, validated_data): if instance.consultation.discharge_date: raise ValidationError( - {"consultation": ["Discharged Consultation data cannot be updated"]} + {"consultation": ["Discharged Consultation data cannot be updated"]}, ) if ( @@ -128,7 +146,7 @@ def update(self, instance, validated_data): instance.consultation.save(update_fields=["review_interval"]) if review_interval >= 0: patient.review_time = localtime(now()) + timedelta( - minutes=review_interval + minutes=review_interval, ) else: patient.review_time = None @@ -160,21 +178,21 @@ def update_last_daily_round(self, daily_round_obj): facility=daily_round_obj.consultation.patient.facility, ).generate() - def create(self, validated_data): + def create(self, validated_data): # noqa: PLR0915 # Authorisation Checks # Skip check for asset user if self.context["request"].user.asset_id is None: allowed_facilities = get_home_facility_queryset( - self.context["request"].user + self.context["request"].user, ) if not allowed_facilities.filter( - id=self.validated_data["consultation"].facility.id + id=self.validated_data["consultation"].facility.id, ).exists(): raise ValidationError( { - "facility": "Daily Round creates are only allowed in home facility" - } + "facility": "Daily Round creates are only allowed in home facility", + }, ) # Authorisation Checks End @@ -185,20 +203,20 @@ def create(self, validated_data): if should_clone: consultation = get_object_or_404( get_consultation_queryset(self.context["request"].user).filter( - id=validated_data["consultation"].id - ) + id=validated_data["consultation"].id, + ), ) last_objects = DailyRound.objects.filter( - consultation=consultation + consultation=consultation, ).order_by("-created_date") if not last_objects.exists(): raise ValidationError( - {"daily_round": "No Daily Round record available to copy"} + {"daily_round": "No Daily Round record available to copy"}, ) if "rounds_type" not in validated_data: raise ValidationError( - {"daily_round": "Rounds type is required to clone"} + {"daily_round": "Rounds type is required to clone"}, ) rounds_type = validated_data.get("rounds_type") @@ -250,12 +268,12 @@ def create(self, validated_data): if "consultation__review_interval" in validated_data: review_interval = validated_data.pop( - "consultation__review_interval" + "consultation__review_interval", ) if review_interval >= 0: validated_data["consultation"].review_interval = review_interval patient.review_time = localtime(now()) + timedelta( - minutes=review_interval + minutes=review_interval, ) else: patient.review_time = None @@ -278,13 +296,13 @@ def create(self, validated_data): "last_updated_by_telemedicine" ] daily_round_obj.consultation.save( - update_fields=["last_updated_by_telemedicine", "review_interval"] + update_fields=["last_updated_by_telemedicine", "review_interval"], ) daily_round_obj.save( update_fields=[ "created_by", "last_edited_by", - ] + ], ) if daily_round_obj.rounds_type != DailyRound.RoundsType.AUTOMATED.value: @@ -296,27 +314,29 @@ def validate(self, obj): if validated["consultation"].discharge_date: raise ValidationError( - {"consultation": ["Discharged Consultation data cannot be updated"]} + {"consultation": ["Discharged Consultation data cannot be updated"]}, ) - if "action" in validated: - if validated["action"] == PatientRegistration.ActionEnum.REVIEW: - if "consultation__review_interval" not in validated: - raise ValidationError( - { - "review_interval": [ - "This field is required as the patient has been requested Review." - ] - } - ) - if validated["consultation__review_interval"] <= 0: - raise ValidationError( - { - "review_interval": [ - "This field value is must be greater than 0." - ] - } - ) + if ( + "action" in validated + and validated["action"] == PatientRegistration.ActionEnum.REVIEW + ): + if "consultation__review_interval" not in validated: + raise ValidationError( + { + "review_interval": [ + "This field is required as the patient has been requested Review.", + ], + }, + ) + if validated["consultation__review_interval"] <= 0: + raise ValidationError( + { + "review_interval": [ + "This field value is must be greater than 0.", + ], + }, + ) if "bed" in validated: external_id = validated.pop("bed")["external_id"] diff --git a/care/facility/api/serializers/facility.py b/care/facility/api/serializers/facility.py index eebd4a5d23..848a6971cf 100644 --- a/care/facility/api/serializers/facility.py +++ b/care/facility/api/serializers/facility.py @@ -14,8 +14,8 @@ WardSerializer, ) from care.utils.csp import config as cs_provider +from care.utils.validators.url_validators import MiddlewareDomainAddressValidator from config.serializers import ChoiceField -from config.validators import MiddlewareDomainAddressValidator User = get_user_model() @@ -57,7 +57,8 @@ def get_bed_count(self, facility): def get_patient_count(self, facility): return PatientRegistration.objects.filter( - facility=facility, is_active=True + facility=facility, + is_active=True, ).count() def get_facility_type(self, facility): diff --git a/care/facility/api/serializers/facility_capacity.py b/care/facility/api/serializers/facility_capacity.py index 887f386235..6abd0c0702 100644 --- a/care/facility/api/serializers/facility_capacity.py +++ b/care/facility/api/serializers/facility_capacity.py @@ -17,8 +17,8 @@ def validate(self, data): ): raise serializers.ValidationError( { - "current_capacity": "Current capacity cannot be greater than total capacity." - } + "current_capacity": "Current capacity cannot be greater than total capacity.", + }, ) return data diff --git a/care/facility/api/serializers/file_upload.py b/care/facility/api/serializers/file_upload.py index 9515885917..9156a228ec 100644 --- a/care/facility/api/serializers/file_upload.py +++ b/care/facility/api/serializers/file_upload.py @@ -13,46 +13,52 @@ from config.serializers import ChoiceField -def check_permissions(file_type, associating_id, user, action="create"): +def check_permissions( # noqa: PLR0911, PLR0912 + file_type, + associating_id, + user, + action="create", +): try: if file_type == FileUpload.FileType.PATIENT.value: patient = PatientRegistration.objects.get(external_id=associating_id) if not patient.is_active: raise serializers.ValidationError( - {"patient": "Cannot upload file for a discharged patient."} + {"patient": "Cannot upload file for a discharged patient."}, ) - if patient.assigned_to: - if user == patient.assigned_to: - return patient.id - if patient.last_consultation: - if patient.last_consultation.assigned_to: - if user == patient.last_consultation.assigned_to: - return patient.id + if patient.assigned_to and user == patient.assigned_to: + return patient.id + if ( + patient.last_consultation + and patient.last_consultation.assigned_to + and user == patient.last_consultation.assigned_to + ): + return patient.id if not has_facility_permission(user, patient.facility): raise Exception("No Permission") return patient.id - elif file_type == FileUpload.FileType.CONSULTATION.value: + if file_type == FileUpload.FileType.CONSULTATION.value: consultation = PatientConsultation.objects.get(external_id=associating_id) - if consultation.discharge_date: - if not action == "read": - raise serializers.ValidationError( - { - "consultation": "Cannot upload file for a discharged consultation." - } - ) - if consultation.patient.assigned_to: - if user == consultation.patient.assigned_to: - return consultation.id - if consultation.assigned_to: - if user == consultation.assigned_to: - return consultation.id + if consultation.discharge_date and not action == "read": + raise serializers.ValidationError( + { + "consultation": "Cannot upload file for a discharged consultation.", + }, + ) + if ( + consultation.patient.assigned_to + and user == consultation.patient.assigned_to + ): + return consultation.id + if consultation.assigned_to and user == consultation.assigned_to: + return consultation.id if not ( has_facility_permission(user, consultation.patient.facility) or has_facility_permission(user, consultation.facility) ): raise Exception("No Permission") return consultation.id - elif file_type == FileUpload.FileType.DISCHARGE_SUMMARY.value: + if file_type == FileUpload.FileType.DISCHARGE_SUMMARY.value: consultation = PatientConsultation.objects.get(external_id=associating_id) if ( consultation.patient.assigned_to @@ -67,36 +73,35 @@ def check_permissions(file_type, associating_id, user, action="create"): ): raise Exception("No Permission") return consultation.external_id - elif file_type == FileUpload.FileType.SAMPLE_MANAGEMENT.value: + if file_type == FileUpload.FileType.SAMPLE_MANAGEMENT.value: sample = PatientSample.objects.get(external_id=associating_id) patient = sample.patient - if patient.assigned_to: - if user == patient.assigned_to: - return sample.id - if sample.consultation: - if sample.consultation.assigned_to: - if user == sample.consultation.assigned_to: - return sample.id - if sample.testing_facility: - if has_facility_permission( - user, - Facility.objects.get( - external_id=sample.testing_facility.external_id - ), - ): - return sample.id + if patient.assigned_to and user == patient.assigned_to: + return sample.id + if ( + sample.consultation + and sample.consultation.assigned_to + and user == sample.consultation.assigned_to + ): + return sample.id + if sample.testing_facility and has_facility_permission( + user, + Facility.objects.get( + external_id=sample.testing_facility.external_id, + ), + ): + return sample.id if not has_facility_permission(user, patient.facility): raise Exception("No Permission") return sample.id - elif file_type == FileUpload.FileType.CLAIM.value: + if file_type == FileUpload.FileType.CLAIM.value: return associating_id - elif file_type == FileUpload.FileType.COMMUNICATION.value: + if file_type == FileUpload.FileType.COMMUNICATION.value: return associating_id - else: - raise Exception("Undefined File Type") + raise Exception("Undefined File Type") except Exception: - raise serializers.ValidationError({"permission": "denied"}) + raise serializers.ValidationError({"permission": "denied"}) from None class FileUploadCreateSerializer(serializers.ModelSerializer): @@ -126,7 +131,9 @@ class Meta: def create(self, validated_data): user = self.context["request"].user internal_id = check_permissions( - validated_data["file_type"], validated_data["associating_id"], user + validated_data["file_type"], + validated_data["associating_id"], + user, ) validated_data["associating_id"] = internal_id validated_data["uploaded_by"] = user @@ -179,14 +186,14 @@ def update(self, instance, validated_data): user = self.context["request"].user if instance.is_archived: raise serializers.ValidationError( - {"file": "Operation not permitted when archived."} + {"file": "Operation not permitted when archived."}, ) if user.user_type <= User.TYPE_VALUE_MAP["LocalBodyAdmin"]: if instance.uploaded_by == user: pass else: raise serializers.ValidationError( - {"permission": "Don't have permission to archive"} + {"permission": "Don't have permission to archive"}, ) file = super().update(instance, validated_data) if file.is_archived: diff --git a/care/facility/api/serializers/inventory.py b/care/facility/api/serializers/inventory.py index e035137a5c..2b4e9d3dc5 100644 --- a/care/facility/api/serializers/inventory.py +++ b/care/facility/api/serializers/inventory.py @@ -1,3 +1,4 @@ +import contextlib from datetime import timedelta from django.db import IntegrityError, transaction @@ -70,20 +71,21 @@ def create(self, validated_data): item.allowed_units.get(id=unit.id) except FacilityInventoryUnit.DoesNotExist: raise serializers.ValidationError( - {"unit": ["Item cannot be measured with unit"]} - ) + {"unit": ["Item cannot be measured with unit"]}, + ) from None multiplier = 1 try: if item.default_unit != unit: multiplier = FacilityInventoryUnitConverter.objects.get( - from_unit=unit, to_unit=item.default_unit + from_unit=unit, + to_unit=item.default_unit, ).multiplier except FacilityInventoryUnitConverter.DoesNotExist: raise serializers.ValidationError( - {"item": ["Please Ask Admin to Add Conversion Metrics"]} - ) + {"item": ["Please Ask Admin to Add Conversion Metrics"]}, + ) from None validated_data["created_by"] = self.context["request"].user @@ -96,7 +98,8 @@ def create(self, validated_data): validated_data["quantity_in_default_unit"] = abs(current_quantity) try: summary_obj = FacilityInventorySummary.objects.get( - facility=facility, item=item + facility=facility, + item=item, ) current_quantity = summary_obj.quantity + ( multiplier * validated_data["quantity"] @@ -114,12 +117,11 @@ def create(self, validated_data): if current_quantity < 0: raise serializers.ValidationError({"stock": ["Stock not Available"]}) - try: + with contextlib.suppress(FacilityInventoryMinQuantity.DoesNotExist): current_min_quantity = FacilityInventoryMinQuantity.objects.get( - facility=facility, item=item + facility=facility, + item=item, ).min_quantity - except FacilityInventoryMinQuantity.DoesNotExist: - pass summary_obj.is_low = current_quantity < current_min_quantity @@ -153,7 +155,9 @@ def set_burn_rate(facility, item): if previous_usage_log_sum["quantity_in_default_unit__sum"]: burn_rate = previous_usage_log_sum["quantity_in_default_unit__sum"] / 24 FacilityInventoryBurnRate.objects.update_or_create( - facility=facility, item=item, defaults={"burn_rate": burn_rate} + facility=facility, + item=item, + defaults={"burn_rate": burn_rate}, ) @@ -199,12 +203,13 @@ def create(self, validated_data): instance = super().create(validated_data) except IntegrityError: raise serializers.ValidationError( - {"item": ["Item min quantity already set"]} - ) + {"item": ["Item min quantity already set"]}, + ) from None try: summary_obj = FacilityInventorySummary.objects.get( - facility=validated_data["facility"], item=item + facility=validated_data["facility"], + item=item, ) summary_obj.is_low = summary_obj.quantity < validated_data["min_quantity"] summary_obj.save() @@ -214,15 +219,15 @@ def create(self, validated_data): return instance def update(self, instance, validated_data): - if "item" in validated_data: - if instance.item != validated_data["item"]: - raise serializers.ValidationError({"item": ["Item cannot be Changed"]}) + if "item" in validated_data and instance.item != validated_data["item"]: + raise serializers.ValidationError({"item": ["Item cannot be Changed"]}) item = validated_data["item"] try: summary_obj = FacilityInventorySummary.objects.get( - facility=instance.facility, item=item + facility=instance.facility, + item=item, ) summary_obj.is_low = summary_obj.quantity < validated_data["min_quantity"] summary_obj.save() diff --git a/care/facility/api/serializers/patient.py b/care/facility/api/serializers/patient.py index d3b47f24c6..b8f967cfb7 100644 --- a/care/facility/api/serializers/patient.py +++ b/care/facility/api/serializers/patient.py @@ -3,7 +3,7 @@ from django.conf import settings from django.db import transaction from django.db.models import Q -from django.utils.timezone import localtime, make_aware, now +from django.utils import timezone from rest_framework import serializers from care.abdm.api.serializers.abhanumber import AbhaNumberSerializer @@ -63,7 +63,9 @@ class Meta: class PatientListSerializer(serializers.ModelSerializer): id = serializers.CharField(source="external_id", read_only=True) facility = serializers.UUIDField( - source="facility.external_id", allow_null=True, read_only=True + source="facility.external_id", + allow_null=True, + read_only=True, ) facility_object = FacilityBasicInfoSerializer(source="facility", read_only=True) ward_object = WardSerializer(source="ward", read_only=True) @@ -75,7 +77,8 @@ class PatientListSerializer(serializers.ModelSerializer): blood_group = ChoiceField(choices=BLOOD_GROUP_CHOICES, required=True) disease_status = ChoiceField( - choices=DISEASE_STATUS_CHOICES, default=DiseaseStatusEnum.SUSPECTED.value + choices=DISEASE_STATUS_CHOICES, + default=DiseaseStatusEnum.SUSPECTED.value, ) source = ChoiceField(choices=PatientRegistration.SourceChoices) @@ -83,7 +86,8 @@ class PatientListSerializer(serializers.ModelSerializer): # HCX has_eligible_policy = serializers.SerializerMethodField( - "get_has_eligible_policy", read_only=True + "get_has_eligible_policy", + read_only=True, ) def get_has_eligible_policy(self, patient): @@ -95,7 +99,8 @@ def get_has_eligible_policy(self, patient): return bool(len(eligible_policies)) approved_claim_amount = serializers.SerializerMethodField( - "get_approved_claim_amount", read_only=True + "get_approved_claim_amount", + read_only=True, ) def get_approved_claim_amount(self, patient): @@ -111,6 +116,7 @@ def get_approved_claim_amount(self, patient): .first() ) return claim.total_claim_amount if claim is not None else None + return None class Meta: model = PatientRegistration @@ -132,7 +138,8 @@ class PatientContactDetailsSerializer(serializers.ModelSerializer): mode_of_contact = ChoiceField(choices=PatientContactDetails.ModeOfContactChoices) patient_in_contact_object = PatientListSerializer( - read_only=True, source="patient_in_contact" + read_only=True, + source="patient_in_contact", ) patient_in_contact = serializers.UUIDField(source="patient_in_contact.external_id") @@ -163,14 +170,17 @@ class Meta: fields = "__all__" facility = ExternalIdSerializerField( - queryset=Facility.objects.all(), required=False + queryset=Facility.objects.all(), + required=False, ) medical_history = serializers.ListSerializer( - child=MedicalHistorySerializer(), required=False + child=MedicalHistorySerializer(), + required=False, ) tele_consultation_history = serializers.ListSerializer( - child=PatientTeleConsultationSerializer(), read_only=True + child=PatientTeleConsultationSerializer(), + read_only=True, ) last_consultation = PatientConsultationSerializer(read_only=True) facility_object = FacilitySerializer(source="facility", read_only=True) @@ -183,12 +193,15 @@ class Meta: default=PatientRegistration.SourceEnum.CARE.value, ) disease_status = ChoiceField( - choices=DISEASE_STATUS_CHOICES, default=DiseaseStatusEnum.SUSPECTED.value + choices=DISEASE_STATUS_CHOICES, + default=DiseaseStatusEnum.SUSPECTED.value, ) meta_info = PatientMetaInfoSerializer(required=False, allow_null=True) contacted_patients = PatientContactDetailsSerializer( - many=True, required=False, allow_null=True + many=True, + required=False, + allow_null=True, ) test_type = ChoiceField( @@ -200,19 +213,25 @@ class Meta: last_edited = UserBaseMinimumSerializer(read_only=True) created_by = UserBaseMinimumSerializer(read_only=True) vaccine_name = serializers.ChoiceField( - choices=PatientRegistration.vaccineChoices, required=False, allow_null=True + choices=PatientRegistration.VaccineChoices, + required=False, + allow_null=True, ) assigned_to_object = UserBaseMinimumSerializer(source="assigned_to", read_only=True) assigned_to = serializers.PrimaryKeyRelatedField( - queryset=User.objects.all(), required=False, allow_null=True + queryset=User.objects.all(), + required=False, + allow_null=True, ) allow_transfer = serializers.BooleanField(default=settings.PEACETIME_MODE) abha_number = ExternalIdSerializerField( - queryset=AbhaNumber.objects.all(), required=False, allow_null=True + queryset=AbhaNumber.objects.all(), + required=False, + allow_null=True, ) abha_number_object = AbhaNumberSerializer(source="abha_number", read_only=True) @@ -253,7 +272,7 @@ def validate(self, attrs): and not validated.get("date_of_birth") ): raise serializers.ValidationError( - {"non_field_errors": ["Either age or date_of_birth should be passed"]} + {"non_field_errors": ["Either age or date_of_birth should be passed"]}, ) if validated.get("is_vaccinated"): @@ -267,7 +286,7 @@ def validate(self, attrs): def check_external_entry(self, srf_id): if srf_id: PatientExternalTest.objects.filter(srf_id__iexact=srf_id).update( - patient_created=True + patient_created=True, ) def create(self, validated_data): @@ -278,26 +297,25 @@ def create(self, validated_data): if "facility" not in validated_data: raise serializers.ValidationError( - {"facility": "Facility is required to register a patient"} + {"facility": "Facility is required to register a patient"}, ) # Authorization checks allowed_facilities = get_home_facility_queryset( - self.context["request"].user + self.context["request"].user, ) if not allowed_facilities.filter( - id=self.validated_data["facility"].id + id=self.validated_data["facility"].id, ).exists(): raise serializers.ValidationError( - {"facility": "Patient can only be created in the home facility"} + {"facility": "Patient can only be created in the home facility"}, ) # Authorisation checks end - if "srf_id" in validated_data: - if validated_data["srf_id"]: - self.check_external_entry(validated_data["srf_id"]) + if "srf_id" in validated_data and validated_data["srf_id"]: + self.check_external_entry(validated_data["srf_id"]) validated_data["created_by"] = self.context["request"].user patient = super().create(validated_data) @@ -342,12 +360,14 @@ def update(self, instance, validated_data): external_id = validated_data.pop("facility")["external_id"] if external_id: validated_data["facility_id"] = Facility.objects.get( - external_id=external_id + external_id=external_id, ).id - if "srf_id" in validated_data: - if instance.srf_id != validated_data["srf_id"]: - self.check_external_entry(validated_data["srf_id"]) + if ( + "srf_id" in validated_data + and instance.srf_id != validated_data["srf_id"] + ): + self.check_external_entry(validated_data["srf_id"]) patient = super().update(instance, validated_data) Disease.objects.filter(patient=patient).update(deleted=True) @@ -388,10 +408,11 @@ def update(self, instance, validated_data): class FacilityPatientStatsHistorySerializer(serializers.ModelSerializer): id = serializers.CharField(source="external_id", read_only=True) entry_date = serializers.DateField( - default=make_aware(datetime.datetime.today()).date() + default=datetime.date.today, ) facility = ExternalIdSerializerField( - queryset=Facility.objects.all(), read_only=True + queryset=Facility.objects.all(), + read_only=True, ) class Meta: @@ -435,7 +456,8 @@ class Meta: class PatientTransferSerializer(serializers.ModelSerializer): facility_object = FacilityBasicInfoSerializer(source="facility", read_only=True) facility = ExternalIdSerializerField( - write_only=True, queryset=Facility.objects.all() + write_only=True, + queryset=Facility.objects.all(), ) patient = serializers.UUIDField(source="external_id", read_only=True) @@ -454,8 +476,9 @@ def create(self, validated_data): def save(self, **kwargs): self.instance.facility = self.validated_data["facility"] PatientConsultation.objects.filter( - patient=self.instance, discharge_date__isnull=True - ).update(discharge_date=localtime(now())) + patient=self.instance, + discharge_date__isnull=True, + ).update(discharge_date=timezone.now()) self.instance.save() diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index bc86b8515e..5db4cd865f 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -49,35 +49,45 @@ class PatientConsultationSerializer(serializers.ModelSerializer): symptoms = serializers.MultipleChoiceField(choices=SYMPTOM_CHOICES) deprecated_covid_category = ChoiceField( - choices=COVID_CATEGORY_CHOICES, required=False + choices=COVID_CATEGORY_CHOICES, + required=False, ) category = ChoiceField(choices=CATEGORY_CHOICES, required=True) referred_to_object = FacilityBasicInfoSerializer( - source="referred_to", read_only=True + source="referred_to", + read_only=True, ) referred_to = ExternalIdSerializerField( queryset=Facility.objects.all(), required=False, ) referred_to_external = serializers.CharField( - required=False, allow_null=True, allow_blank=True + required=False, + allow_null=True, + allow_blank=True, ) patient = ExternalIdSerializerField(queryset=PatientRegistration.objects.all()) facility = ExternalIdSerializerField(read_only=True) assigned_to_object = UserAssignedSerializer(source="assigned_to", read_only=True) assigned_to = serializers.PrimaryKeyRelatedField( - queryset=User.objects.all(), required=False, allow_null=True + queryset=User.objects.all(), + required=False, + allow_null=True, ) verified_by_object = UserBaseMinimumSerializer(source="verified_by", read_only=True) verified_by = serializers.PrimaryKeyRelatedField( - queryset=User.objects.all(), required=False, allow_null=True + queryset=User.objects.all(), + required=False, + allow_null=True, ) discharge_reason = serializers.ChoiceField( - choices=DISCHARGE_REASON_CHOICES, read_only=True, required=False + choices=DISCHARGE_REASON_CHOICES, + read_only=True, + required=False, ) discharge_notes = serializers.CharField(read_only=True) @@ -103,7 +113,7 @@ class PatientConsultationSerializer(serializers.ModelSerializer): icd11_diagnoses_object = serializers.SerializerMethodField(read_only=True) icd11_provisional_diagnoses_object = serializers.SerializerMethodField( - read_only=True + read_only=True, ) def get_discharge_prescription(self, consultation): @@ -125,7 +135,7 @@ def get_icd11_diagnoses_object(self, consultation): def get_icd11_provisional_diagnoses_object(self, consultation): return get_icd11_diagnoses_objects_by_ids( - consultation.icd11_provisional_diagnoses + consultation.icd11_provisional_diagnoses, ) class Meta: @@ -154,7 +164,7 @@ def update(self, instance, validated_data): if instance.discharge_date: raise ValidationError( - {"consultation": ["Discharged Consultation data cannot be updated"]} + {"consultation": ["Discharged Consultation data cannot be updated"]}, ) if instance.suggestion == SuggestionChoices.OP: @@ -174,7 +184,7 @@ def update(self, instance, validated_data): instance.save() if review_interval >= 0: patient.review_time = localtime(now()) + timedelta( - minutes=review_interval + minutes=review_interval, ) else: patient.review_time = None @@ -184,26 +194,32 @@ def update(self, instance, validated_data): self.context["request"].user == instance.assigned_to ) - if "is_kasp" in validated_data: - if validated_data["is_kasp"] and (not instance.is_kasp): - validated_data["kasp_enabled_date"] = localtime(now()) + if ( + "is_kasp" in validated_data + and validated_data["is_kasp"] + and (not instance.is_kasp) + ): + validated_data["kasp_enabled_date"] = localtime(now()) _temp = instance.assigned_to consultation = super().update(instance, validated_data) - if "assigned_to" in validated_data: - if validated_data["assigned_to"] != _temp and validated_data["assigned_to"]: - NotificationGenerator( - event=Notification.Event.PATIENT_CONSULTATION_ASSIGNMENT, - caused_by=self.context["request"].user, - caused_object=instance, - facility=instance.patient.facility, - notification_mediums=[ - Notification.Medium.SYSTEM, - Notification.Medium.WHATSAPP, - ], - ).generate() + if ( + "assigned_to" in validated_data + and validated_data["assigned_to"] != _temp + and validated_data["assigned_to"] + ): + NotificationGenerator( + event=Notification.Event.PATIENT_CONSULTATION_ASSIGNMENT, + caused_by=self.context["request"].user, + caused_object=instance, + facility=instance.patient.facility, + notification_mediums=[ + Notification.Medium.SYSTEM, + Notification.Medium.WHATSAPP, + ], + ).generate() NotificationGenerator( event=Notification.Event.PATIENT_CONSULTATION_UPDATED, @@ -214,7 +230,7 @@ def update(self, instance, validated_data): return consultation - def create(self, validated_data): + def create(self, validated_data): # noqa: PLR0912 action = -1 review_interval = -1 if "action" in validated_data: @@ -226,34 +242,34 @@ def create(self, validated_data): allowed_facilities = get_home_facility_queryset(self.context["request"].user) if not allowed_facilities.filter( - id=self.validated_data["patient"].facility.id + id=self.validated_data["patient"].facility.id, ).exists(): raise ValidationError( - {"facility": "Consultation creates are only allowed in home facility"} + {"facility": "Consultation creates are only allowed in home facility"}, ) # End Authorisation Checks - if validated_data["patient"].last_consultation: - if ( - self.context["request"].user - == validated_data["patient"].last_consultation.assigned_to - ): - raise ValidationError( - { - "Permission Denied": "Only Facility Staff can create consultation for a Patient" - }, - ) + if validated_data["patient"].last_consultation and ( + self.context["request"].user + == validated_data["patient"].last_consultation.assigned_to + ): + raise ValidationError( + { + "Permission Denied": "Only Facility Staff can create consultation for a Patient", + }, + ) - if validated_data["patient"].last_consultation: - if not validated_data["patient"].last_consultation.discharge_date: - raise ValidationError( - {"consultation": "Exists please Edit Existing Consultation"} - ) + if ( + validated_data["patient"].last_consultation + and not validated_data["patient"].last_consultation.discharge_date + ): + raise ValidationError( + {"consultation": "Exists please Edit Existing Consultation"}, + ) - if "is_kasp" in validated_data: - if validated_data["is_kasp"]: - validated_data["kasp_enabled_date"] = localtime(now()) + if "is_kasp" in validated_data and validated_data["is_kasp"]: + validated_data["kasp_enabled_date"] = localtime(now()) bed = validated_data.pop("bed", None) @@ -325,7 +341,7 @@ def create(self, validated_data): return consultation - def validate(self, attrs): + def validate(self, attrs): # noqa: PLR0912, PLR0915 validated = super().validate(attrs) # TODO Add Bed Authorisation Validation @@ -337,9 +353,9 @@ def validate(self, attrs): raise ValidationError( { "verified_by": [ - "This field is required as the suggestion is not 'Declared Death'" - ] - } + "This field is required as the suggestion is not 'Declared Death'", + ], + }, ) if not validated["verified_by"].user_type == User.TYPE_VALUE_MAP["Doctor"]: raise ValidationError("Only Doctors can verify a Consultation") @@ -354,20 +370,20 @@ def validate(self, attrs): and validated["verified_by"].home_facility != facility ): raise ValidationError( - "Home Facility of the Doctor must be the same as the Consultation Facility" + "Home Facility of the Doctor must be the same as the Consultation Facility", ) if "suggestion" in validated: if validated["suggestion"] is SuggestionChoices.R: if not validated.get("referred_to") and not validated.get( - "referred_to_external" + "referred_to_external", ): raise ValidationError( { "referred_to": [ - f"This field is required as the suggestion is {SuggestionChoices.R}." - ] - } + f"This field is required as the suggestion is {SuggestionChoices.R}.", + ], + }, ) if validated.get("referred_to_external"): validated["referred_to"] = None @@ -377,32 +393,34 @@ def validate(self, attrs): if not validated.get("admission_date"): raise ValidationError( { - "admission_date": "This field is required as the patient has been admitted." - } + "admission_date": "This field is required as the patient has been admitted.", + }, ) if validated["admission_date"] > now(): raise ValidationError( - {"admission_date": "This field value cannot be in the future."} + {"admission_date": "This field value cannot be in the future."}, ) - if "action" in validated: - if validated["action"] == PatientRegistration.ActionEnum.REVIEW: - if "review_interval" not in validated: - raise ValidationError( - { - "review_interval": [ - "This field is required as the patient has been requested Review." - ] - } - ) - if validated["review_interval"] <= 0: - raise ValidationError( - { - "review_interval": [ - "This field value is must be greater than 0." - ] - } - ) + if ( + "action" in validated + and validated["action"] == PatientRegistration.ActionEnum.REVIEW + ): + if "review_interval" not in validated: + raise ValidationError( + { + "review_interval": [ + "This field is required as the patient has been requested Review.", + ], + }, + ) + if validated["review_interval"] <= 0: + raise ValidationError( + { + "review_interval": [ + "This field value is must be greater than 0.", + ], + }, + ) from care.facility.static_data.icd11 import ICDDiseases final_diagnosis = [] @@ -417,10 +435,10 @@ def validate(self, attrs): raise ValidationError( { "icd11_diagnoses": [ - f"{diagnosis} is not a valid ICD 11 Diagnosis ID" - ] - } - ) + f"{diagnosis} is not a valid ICD 11 Diagnosis ID", + ], + }, + ) from None if "icd11_provisional_diagnoses" in validated: for diagnosis in validated["icd11_provisional_diagnoses"]: @@ -431,10 +449,10 @@ def validate(self, attrs): raise ValidationError( { "icd11_provisional_diagnoses": [ - f"{diagnosis} is not a valid ICD 11 Diagnosis ID" - ] - } - ) + f"{diagnosis} is not a valid ICD 11 Diagnosis ID", + ], + }, + ) from None if ( "icd11_principal_diagnosis" in validated @@ -445,29 +463,29 @@ def validate(self, attrs): raise ValidationError( { "icd11_principal_diagnosis": [ - "Principal Diagnosis must be one of the Final Diagnosis" - ] - } + "Principal Diagnosis must be one of the Final Diagnosis", + ], + }, ) elif len(provisional_diagnosis): if validated["icd11_principal_diagnosis"] not in provisional_diagnosis: raise ValidationError( { "icd11_principal_diagnosis": [ - "Principal Diagnosis must be one of the Provisional Diagnosis" - ] - } + "Principal Diagnosis must be one of the Provisional Diagnosis", + ], + }, ) else: raise ValidationError( { "icd11_diagnoses": [ - "Atleast one diagnosis is required for final diagnosis" + "Atleast one diagnosis is required for final diagnosis", ], "icd11_provisional_diagnoses": [ - "Atleast one diagnosis is required for provisional diagnosis" + "Atleast one diagnosis is required for provisional diagnosis", ], - } + }, ) return validated @@ -475,7 +493,8 @@ def validate(self, attrs): class PatientConsultationDischargeSerializer(serializers.ModelSerializer): discharge_reason = serializers.ChoiceField( - choices=DISCHARGE_REASON_CHOICES, required=True + choices=DISCHARGE_REASON_CHOICES, + required=True, ) discharge_notes = serializers.CharField(required=False, allow_blank=True) @@ -492,7 +511,9 @@ class PatientConsultationDischargeSerializer(serializers.ModelSerializer): allow_null=True, ) referred_to_external = serializers.CharField( - required=False, allow_blank=True, allow_null=True + required=False, + allow_blank=True, + allow_null=True, ) def get_discharge_prescription(self, consultation): @@ -528,12 +549,12 @@ def validate(self, attrs): raise ValidationError( { "referred_to": [ - "Only one of referred_to and referred_to_external can be set" + "Only one of referred_to and referred_to_external can be set", ], "referred_to_external": [ - "Only one of referred_to and referred_to_external can be set" + "Only one of referred_to and referred_to_external can be set", ], - } + }, ) if attrs.get("discharge_reason") != "EXP": attrs.pop("death_datetime", None) @@ -544,7 +565,7 @@ def validate(self, attrs): raise ValidationError({"death_datetime": "This field is required"}) if attrs.get("death_datetime") > now(): raise ValidationError( - {"death_datetime": "This field value cannot be in the future."} + {"death_datetime": "This field value cannot be in the future."}, ) if ( self.instance.admission_date @@ -552,19 +573,19 @@ def validate(self, attrs): ): raise ValidationError( { - "death_datetime": "This field value cannot be before the admission date." - } + "death_datetime": "This field value cannot be before the admission date.", + }, ) if not attrs.get("death_confirmed_doctor"): raise ValidationError( - {"death_confirmed_doctor": "This field is required"} + {"death_confirmed_doctor": "This field is required"}, ) attrs["discharge_date"] = attrs["death_datetime"] elif not attrs.get("discharge_date"): raise ValidationError({"discharge_date": "This field is required"}) elif attrs.get("discharge_date") > now(): raise ValidationError( - {"discharge_date": "This field value cannot be in the future."} + {"discharge_date": "This field value cannot be in the future."}, ) elif ( self.instance.admission_date @@ -572,8 +593,8 @@ def validate(self, attrs): ): raise ValidationError( { - "discharge_date": "This field value cannot be before the admission date." - } + "discharge_date": "This field value cannot be before the admission date.", + }, ) return attrs @@ -586,7 +607,8 @@ def save(self, **kwargs): patient.review_time = None patient.save(update_fields=["allow_transfer", "is_active", "review_time"]) ConsultationBed.objects.filter( - consultation=self.instance, end_date__isnull=True + consultation=self.instance, + end_date__isnull=True, ).update(end_date=now()) if patient.abha_number: abha_number = patient.abha_number @@ -598,7 +620,7 @@ def save(self, **kwargs): "dateOfBirth": str(abha_number.date_of_birth), "consultationId": abha_number.external_id, "purpose": "LINK", - } + }, ) return instance diff --git a/care/facility/api/serializers/patient_external_test.py b/care/facility/api/serializers/patient_external_test.py index 677c6b2e74..3cddff05c8 100644 --- a/care/facility/api/serializers/patient_external_test.py +++ b/care/facility/api/serializers/patient_external_test.py @@ -19,11 +19,12 @@ class PatientExternalTestSerializer(serializers.ModelSerializer): local_body_type = serializers.CharField(required=False, write_only=True) sample_collection_date = serializers.DateField( - input_formats=["%Y-%m-%d"], required=False + input_formats=["%Y-%m-%d"], + required=False, ) result_date = serializers.DateField(input_formats=["%Y-%m-%d"], required=False) - def validate_empty_values(self, data, *args, **kwargs): + def validate_empty_values(self, data, *args, **kwargs): # noqa: PLR0912 # if "is_repeat" in data: # is_repeat = data["is_repeat"] # if is_repeat.lower() == "yes": @@ -43,12 +44,12 @@ def validate_empty_values(self, data, *args, **kwargs): if "local_body_type" not in data: raise ValidationError( - {"local_body_type": ["local_body_type is not present in data"]} + {"local_body_type": ["local_body_type is not present in data"]}, ) if not data["local_body_type"]: raise ValidationError( - {"local_body_type": ["local_body_type cannot be empty"]} + {"local_body_type": ["local_body_type cannot be empty"]}, ) if data["local_body_type"].lower() not in REVERSE_LOCAL_BODY_CHOICES: @@ -77,10 +78,13 @@ def validate_empty_values(self, data, *args, **kwargs): try: int(data["ward"]) except Exception: - raise ValidationError({"ward": ["Ward must be an integer value"]}) + raise ValidationError( + {"ward": ["Ward must be an integer value"]}, + ) from None if data["ward"]: ward_obj = Ward.objects.filter( - number=data["ward"], local_body=local_body_obj + number=data["ward"], + local_body=local_body_obj, ).first() if ward_obj: data["ward"] = ward_obj.id @@ -92,11 +96,13 @@ def validate_empty_values(self, data, *args, **kwargs): return super().validate_empty_values(data, *args, **kwargs) def create(self, validated_data): - if "srf_id" in validated_data: - if PatientRegistration.objects.filter( - srf_id__iexact=validated_data["srf_id"] - ).exists(): - validated_data["patient_created"] = True + if ( + "srf_id" in validated_data + and PatientRegistration.objects.filter( + srf_id__iexact=validated_data["srf_id"], + ).exists() + ): + validated_data["patient_created"] = True return super().create(validated_data) class Meta: @@ -107,10 +113,12 @@ class Meta: class PatientExternalTestUpdateSerializer(serializers.ModelSerializer): local_body = serializers.PrimaryKeyRelatedField( - queryset=LocalBody.objects.all(), required=False + queryset=LocalBody.objects.all(), + required=False, ) ward = serializers.PrimaryKeyRelatedField( - queryset=Ward.objects.all(), required=False + queryset=Ward.objects.all(), + required=False, ) class Meta: @@ -126,7 +134,7 @@ def update(self, instance, validated_data): if validated_data["local_body"].district != instance.district: raise ValidationError( - {"local_body": "Only supported within same district"} + {"local_body": "Only supported within same district"}, ) return super().update(instance, validated_data) diff --git a/care/facility/api/serializers/patient_icmr.py b/care/facility/api/serializers/patient_icmr.py index e252ea4631..8a2e2e3462 100644 --- a/care/facility/api/serializers/patient_icmr.py +++ b/care/facility/api/serializers/patient_icmr.py @@ -50,7 +50,8 @@ class ICMRSpecimenInformationSerializer(serializers.ModelSerializer): lab_pincode = serializers.CharField() icmr_category = ChoiceField( - choices=PatientSampleICMR.PATIENT_ICMR_CATEGORY, required=False + choices=PatientSampleICMR.PATIENT_ICMR_CATEGORY, + required=False, ) class Meta: @@ -73,10 +74,10 @@ class ICMRPatientCategorySerializer(serializers.ModelSerializer): symptomatic_healthcare_worker = serializers.BooleanField(allow_null=True) hospitalized_sari_patient = serializers.BooleanField(allow_null=True) asymptomatic_family_member_of_confirmed_case = serializers.BooleanField( - allow_null=True + allow_null=True, ) asymptomatic_healthcare_worker_without_protection = serializers.BooleanField( - allow_null=True + allow_null=True, ) class Meta: @@ -98,7 +99,7 @@ class ICMRExposureHistorySerializer(serializers.ModelSerializer): travel_end_date = serializers.DateField() contact_with_confirmed_case = serializers.BooleanField( - source="contact_with_confirmed_carrier" + source="contact_with_confirmed_carrier", ) contact_case_name = serializers.CharField() @@ -127,13 +128,13 @@ class ICMRMedicalConditionSerializer(serializers.ModelSerializer): symptoms = serializers.ListSerializer(child=ChoiceField(choices=SYMPTOM_CHOICES)) hospitalization_date = serializers.DateField() hospital_phone_number = serializers.CharField( - source="consultation.facility.phone_number" + source="consultation.facility.phone_number", ) hospital_name = serializers.CharField(source="consultation.facility.name") hospital_pincode = serializers.CharField(source="consultation.facility.pincode") medical_conditions_list = serializers.ListSerializer( - child=ChoiceField(choices=DISEASE_CHOICES) + child=ChoiceField(choices=DISEASE_CHOICES), ) class Meta: diff --git a/care/facility/api/serializers/patient_investigation.py b/care/facility/api/serializers/patient_investigation.py index bc209b1e68..7f68287d6d 100644 --- a/care/facility/api/serializers/patient_investigation.py +++ b/care/facility/api/serializers/patient_investigation.py @@ -43,10 +43,12 @@ class InvestigationValueSerializer(serializers.ModelSerializer): group_object = PatientInvestigationGroupSerializer(source="group", read_only=True) investigation_object = MinimalPatientInvestigationSerializer( - source="investigation", read_only=True + source="investigation", + read_only=True, ) session_object = PatientInvestigationSessionSerializer( - source="session", read_only=True + source="session", + read_only=True, ) class Meta: @@ -62,7 +64,7 @@ class Meta: def update(self, instance, validated_data): if instance.consultation.discharge_date: raise serializers.ValidationError( - {"consultation": ["Discharged Consultation data cannot be updated"]} + {"consultation": ["Discharged Consultation data cannot be updated"]}, ) # Removed since it might flood messages diff --git a/care/facility/api/serializers/patient_otp.py b/care/facility/api/serializers/patient_otp.py index 7457ac8454..e007c1a96e 100644 --- a/care/facility/api/serializers/patient_otp.py +++ b/care/facility/api/serializers/patient_otp.py @@ -1,5 +1,4 @@ -import random -import string +import secrets from datetime import timedelta from django.conf import settings @@ -8,30 +7,21 @@ from rest_framework.exceptions import ValidationError from care.facility.models.patient import PatientMobileOTP -from care.utils.sms.sendSMS import sendSMS +from care.utils import sms -def rand_pass(size): - if not settings.USE_SMS: - return "45612" - generate_pass = "".join( - [random.choice(string.ascii_uppercase + string.digits) for n in range(size)] - ) - - return generate_pass +def rand_pass(size: int) -> str: + return secrets.token_urlsafe(size) def send_sms(otp, phone_number): - if settings.USE_SMS: - sendSMS( - phone_number, - ( - f"Open Healthcare Network Patient Management System Login, OTP is {otp} . " - "Please do not share this Confidential Login Token with anyone else" - ), - ) - else: - print(otp, phone_number) + sms.send( + phone_number, + ( + f"Open Healthcare Network Patient Management System Login, OTP is {otp} . " + "Please do not share this Confidential Login Token with anyone else" + ), + ) class PatientMobileOTPSerializer(serializers.ModelSerializer): @@ -54,9 +44,12 @@ def create(self, validated_data): raise ValidationError({"phone_number": "Max Retries has exceeded"}) otp_obj = super().create(validated_data) - otp = rand_pass(settings.OTP_LENGTH) - send_sms(otp, otp_obj.phone_number) + if settings.USE_SMS: + otp = rand_pass(settings.OTP_LENGTH) + send_sms(otp, otp_obj.phone_number) + else: + otp = "45612" otp_obj.otp = otp otp_obj.save() diff --git a/care/facility/api/serializers/patient_sample.py b/care/facility/api/serializers/patient_sample.py index 2308dcac1b..a0366960ea 100644 --- a/care/facility/api/serializers/patient_sample.py +++ b/care/facility/api/serializers/patient_sample.py @@ -29,38 +29,47 @@ class PatientSampleSerializer(serializers.ModelSerializer): id = serializers.UUIDField(source="external_id", read_only=True) patient_name = serializers.CharField(read_only=True, source="patient.name") patient_has_sari = serializers.BooleanField( - read_only=True, source="patient.has_SARI" + read_only=True, + source="patient.has_SARI", ) patient_has_confirmed_contact = serializers.BooleanField( - read_only=True, source="patient.contact_with_confirmed_carrier" + read_only=True, + source="patient.contact_with_confirmed_carrier", ) patient_has_suspected_contact = serializers.BooleanField( - read_only=True, source="patient.contact_with_suspected_carrier" + read_only=True, + source="patient.contact_with_suspected_carrier", ) patient_travel_history = serializers.JSONField( - read_only=True, source="patient.countries_travelled" + read_only=True, + source="patient.countries_travelled", ) facility = ExternalIdSerializerField(read_only=True, source="consultation.facility") facility_object = FacilityBasicInfoSerializer( - source="consultation.facility", read_only=True + source="consultation.facility", + read_only=True, ) sample_type = ChoiceField(choices=SAMPLE_TYPE_CHOICES, required=False) status = ChoiceField(choices=PatientSample.SAMPLE_TEST_FLOW_CHOICES, required=False) result = ChoiceField( - choices=PatientSample.SAMPLE_TEST_RESULT_CHOICES, required=False + choices=PatientSample.SAMPLE_TEST_RESULT_CHOICES, + required=False, ) icmr_category = ChoiceField( - choices=PatientSample.PATIENT_ICMR_CATEGORY, required=False + choices=PatientSample.PATIENT_ICMR_CATEGORY, + required=False, ) patient = ExternalIdSerializerField( - required=False, queryset=PatientRegistration.objects.all() + required=False, + queryset=PatientRegistration.objects.all(), ) consultation = ExternalIdSerializerField( - required=False, queryset=PatientConsultation.objects.all() + required=False, + queryset=PatientConsultation.objects.all(), ) date_of_sample = serializers.DateTimeField(required=False) @@ -69,10 +78,12 @@ class PatientSampleSerializer(serializers.ModelSerializer): notes = serializers.CharField(required=False, allow_blank=True) testing_facility = ExternalIdSerializerField( - queryset=Facility.objects.all(), required=False + queryset=Facility.objects.all(), + required=False, ) testing_facility_object = FacilityBasicInfoSerializer( - source="testing_facility", read_only=True + source="testing_facility", + read_only=True, ) last_edited_by = UserBaseMinimumSerializer(read_only=True) created_by = UserBaseMinimumSerializer(read_only=True) @@ -91,7 +102,7 @@ def create(self, validated_data): validated_data.pop("status", None) validated_data.pop("result", None) - sample = super(PatientSampleSerializer, self).create(validated_data) + sample = super().create(validated_data) sample.created_by = self.context["request"].user sample.last_edited_by = self.context["request"].user sample.save() @@ -118,23 +129,26 @@ def update(self, instance, validated_data): "COMPLETED" ] except KeyError: - raise ValidationError({"status": ["is required"]}) + raise ValidationError({"status": ["is required"]}) from None valid_choices = PatientSample.SAMPLE_FLOW_RULES[ PatientSample.SAMPLE_TEST_FLOW_CHOICES[instance.status - 1][1] ] if choice not in valid_choices: raise ValidationError( - {"status": ["Next valid choices are: {', '.join(valid_choices)}"]} + {"status": ["Next valid choices are: {', '.join(valid_choices)}"]}, ) if choice != "COMPLETED" and validated_data.get("result"): raise ValidationError( - {"result": ["Result can't be updated unless test is complete"]} + {"result": ["Result can't be updated unless test is complete"]}, ) if choice == "COMPLETED" and not validated_data.get("result"): raise ValidationError({"result": ["is required as the test is complete"]}) - if choice == "COMPLETED" and instance.result != 3: + if ( + choice == "COMPLETED" + and instance.result != PatientSample.SAMPLE_TEST_RESULT_MAP["AWAITING"] + ): raise ValidationError( - {"result": ["cannot change result for completed test."]} + {"result": ["cannot change result for completed test."]}, ) if ( @@ -142,7 +156,7 @@ def update(self, instance, validated_data): and validated_data.get("date_of_result") is not None ): raise ValidationError( - {"date_of_result": ["cannot be provided without result"]} + {"date_of_result": ["cannot be provided without result"]}, ) if not instance.date_of_sample and validated_data.get("status") in [ diff --git a/care/facility/api/serializers/prescription.py b/care/facility/api/serializers/prescription.py index 4af84080ea..dbc0ff1bb9 100644 --- a/care/facility/api/serializers/prescription.py +++ b/care/facility/api/serializers/prescription.py @@ -55,36 +55,38 @@ class Meta: def validate(self, attrs): if "medicine" in attrs: attrs["medicine"] = get_object_or_404( - MedibaseMedicine, external_id=attrs["medicine"] + MedibaseMedicine, + external_id=attrs["medicine"], ) - if not self.instance: - if Prescription.objects.filter( + if ( + not self.instance + and Prescription.objects.filter( consultation__external_id=self.context["request"].parser_context[ "kwargs" ]["consultation_external_id"], medicine=attrs["medicine"], discontinued=False, - ).exists(): - raise serializers.ValidationError( - { - "medicine": ( - "This medicine is already prescribed to this patient. " - "Please discontinue the existing prescription to prescribe again." - ) - } - ) + ).exists() + ): + raise serializers.ValidationError( + { + "medicine": ( + "This medicine is already prescribed to this patient. " + "Please discontinue the existing prescription to prescribe again." + ), + }, + ) if attrs.get("is_prn"): if not attrs.get("indicator"): raise serializers.ValidationError( - {"indicator": "Indicator should be set for PRN prescriptions."} - ) - else: - if not attrs.get("frequency"): - raise serializers.ValidationError( - {"frequency": "Frequency should be set for prescriptions."} + {"indicator": "Indicator should be set for PRN prescriptions."}, ) + elif not attrs.get("frequency"): + raise serializers.ValidationError( + {"frequency": "Frequency should be set for prescriptions."}, + ) return super().validate(attrs) # TODO: Ensure that this medicine is not already prescribed to the same patient and is currently active. @@ -98,11 +100,11 @@ class MedicineAdministrationSerializer(serializers.ModelSerializer): def validate_administered_date(self, value): if value > timezone.now(): raise serializers.ValidationError( - "Administered Date cannot be in the future." + "Administered Date cannot be in the future.", ) if self.context["prescription"].created_date > value: raise serializers.ValidationError( - "Administered Date cannot be before Prescription Date." + "Administered Date cannot be before Prescription Date.", ) return value diff --git a/care/facility/api/serializers/prescription_supplier.py b/care/facility/api/serializers/prescription_supplier.py index 3bcaeb99fb..30a11c79d7 100644 --- a/care/facility/api/serializers/prescription_supplier.py +++ b/care/facility/api/serializers/prescription_supplier.py @@ -30,12 +30,14 @@ class PrescriptionSupplierSerializer(serializers.ModelSerializer): scheme = ChoiceField(choices=PrescriptionSupplier.SchemeChoices) status = ChoiceField(choices=PrescriptionSupplier.StatusChoices) consultation_object = PrescriptionSupplierConsultationSerializer( - source="consultation", read_only=True + source="consultation", + read_only=True, ) facility_object = FacilityBasicInfoSerializer(source="facility", read_only=True) consultation = ExternalIdSerializerField( - required=True, queryset=PatientConsultation.objects.all() + required=True, + queryset=PatientConsultation.objects.all(), ) facility = ExternalIdSerializerField(required=True, queryset=Facility.objects.all()) diff --git a/care/facility/api/serializers/resources.py b/care/facility/api/serializers/resources.py index 3b5957ed6f..3ca6aed87c 100644 --- a/care/facility/api/serializers/resources.py +++ b/care/facility/api/serializers/resources.py @@ -49,32 +49,45 @@ class ResourceRequestSerializer(serializers.ModelSerializer): status = ChoiceField(choices=RESOURCE_STATUS_CHOICES) origin_facility_object = FacilityBasicInfoSerializer( - source="origin_facility", read_only=True, required=False + source="origin_facility", + read_only=True, + required=False, ) approving_facility_object = FacilityBasicInfoSerializer( - source="approving_facility", read_only=True, required=False + source="approving_facility", + read_only=True, + required=False, ) assigned_facility_object = FacilityBasicInfoSerializer( - source="assigned_facility", read_only=True, required=False + source="assigned_facility", + read_only=True, + required=False, ) category = ChoiceField(choices=RESOURCE_CATEGORY_CHOICES) sub_category = ChoiceField(choices=RESOURCE_SUB_CATEGORY_CHOICES) origin_facility = serializers.UUIDField( - source="origin_facility.external_id", allow_null=False, required=True + source="origin_facility.external_id", + allow_null=False, + required=True, ) approving_facility = serializers.UUIDField( - source="approving_facility.external_id", allow_null=False, required=True + source="approving_facility.external_id", + allow_null=False, + required=True, ) assigned_facility = serializers.UUIDField( - source="assigned_facility.external_id", allow_null=True, required=False + source="assigned_facility.external_id", + allow_null=True, + required=False, ) assigned_to_object = UserBaseMinimumSerializer(source="assigned_to", read_only=True) created_by_object = UserBaseMinimumSerializer(source="created_by", read_only=True) last_edited_by_object = UserBaseMinimumSerializer( - source="last_edited_by", read_only=True + source="last_edited_by", + read_only=True, ) def __init__(self, instance=None, **kwargs): @@ -83,33 +96,35 @@ def __init__(self, instance=None, **kwargs): super().__init__(instance=instance, **kwargs) def update(self, instance, validated_data): - LIMITED_RECIEVING_STATUS_ = [] - LIMITED_RECIEVING_STATUS = [ - REVERSE_REQUEST_STATUS_CHOICES[x] for x in LIMITED_RECIEVING_STATUS_ - ] - LIMITED_REQUEST_STATUS_ = [ - "ON HOLD", - "APPROVED", - "REJECTED", - "TRANSPORTATION TO BE ARRANGED", - "TRANSFER IN PROGRESS", - "COMPLETED", - ] - LIMITED_REQUEST_STATUS = [ - REVERSE_REQUEST_STATUS_CHOICES[x] for x in LIMITED_REQUEST_STATUS_ + limited_receiving_statius = [REVERSE_REQUEST_STATUS_CHOICES[x] for x in []] + limited_request_status = [ + REVERSE_REQUEST_STATUS_CHOICES[x] + for x in [ + "ON HOLD", + "APPROVED", + "REJECTED", + "TRANSPORTATION TO BE ARRANGED", + "TRANSFER IN PROGRESS", + "COMPLETED", + ] ] - # LIMITED_ORGIN_STATUS = [] user = self.context["request"].user if "status" in validated_data: - if validated_data["status"] in LIMITED_RECIEVING_STATUS: - if instance.assigned_facility: - if not has_facility_permission(user, instance.assigned_facility): - raise ValidationError({"status": ["Permission Denied"]}) - elif validated_data["status"] in LIMITED_REQUEST_STATUS: - if not has_facility_permission(user, instance.approving_facility): + if validated_data["status"] in limited_receiving_statius: + if instance.assigned_facility and not has_facility_permission( + user, + instance.assigned_facility, + ): raise ValidationError({"status": ["Permission Denied"]}) + elif validated_data["status"] in limited_request_status and ( + not has_facility_permission( + user, + instance.approving_facility, + ) + ): + raise ValidationError({"status": ["Permission Denied"]}) # Dont allow editing origin or patient if "origin_facility" in validated_data: @@ -121,7 +136,7 @@ def update(self, instance, validated_data): ] if approving_facility_external_id: validated_data["approving_facility_id"] = Facility.objects.get( - external_id=approving_facility_external_id + external_id=approving_facility_external_id, ).id if "assigned_facility" in validated_data: @@ -130,14 +145,12 @@ def update(self, instance, validated_data): ] if assigned_facility_external_id: validated_data["assigned_facility_id"] = Facility.objects.get( - external_id=assigned_facility_external_id + external_id=assigned_facility_external_id, ).id instance.last_edited_by = self.context["request"].user - new_instance = super().update(instance, validated_data) - - return new_instance + return super().update(instance, validated_data) def create(self, validated_data): # Do Validity checks for each of these data @@ -148,14 +161,14 @@ def create(self, validated_data): "external_id" ] validated_data["origin_facility_id"] = Facility.objects.get( - external_id=origin_facility_external_id + external_id=origin_facility_external_id, ).id request_approving_facility_external_id = validated_data.pop( - "approving_facility" + "approving_facility", )["external_id"] validated_data["approving_facility_id"] = Facility.objects.get( - external_id=request_approving_facility_external_id + external_id=request_approving_facility_external_id, ).id if "assigned_facility" in validated_data: @@ -164,7 +177,7 @@ def create(self, validated_data): ] if assigned_facility_external_id: validated_data["assigned_facility_id"] = Facility.objects.get( - external_id=assigned_facility_external_id + external_id=assigned_facility_external_id, ).id validated_data["created_by"] = self.context["request"].user diff --git a/care/facility/api/serializers/shifting.py b/care/facility/api/serializers/shifting.py index 8951a7691b..2cc251ed37 100644 --- a/care/facility/api/serializers/shifting.py +++ b/care/facility/api/serializers/shifting.py @@ -79,7 +79,8 @@ def discharge_patient(patient: PatientRegistration): last_consultation.save() ConsultationBed.objects.filter( - consultation=last_consultation, end_date__isnull=True + consultation=last_consultation, + end_date__isnull=True, ).update(end_date=current_time) @@ -179,52 +180,73 @@ class ShiftingSerializer(serializers.ModelSerializer): status = ChoiceField(choices=SHIFTING_STATUS_CHOICES) breathlessness_level = ChoiceField( - choices=BREATHLESSNESS_CHOICES, required=False, allow_null=True + choices=BREATHLESSNESS_CHOICES, + required=False, + allow_null=True, ) origin_facility = ExternalIdSerializerField( - queryset=Facility.objects.all(), allow_null=False, required=True + queryset=Facility.objects.all(), + allow_null=False, + required=True, ) origin_facility_object = FacilityBasicInfoSerializer( - source="origin_facility", read_only=True + source="origin_facility", + read_only=True, ) shifting_approving_facility = ExternalIdSerializerField( - queryset=Facility.objects.all(), required=False + queryset=Facility.objects.all(), + required=False, ) shifting_approving_facility_object = FacilityBasicInfoSerializer( - source="shifting_approving_facility", read_only=True + source="shifting_approving_facility", + read_only=True, ) assigned_facility = ExternalIdSerializerField( - queryset=Facility.objects.all(), allow_null=True, required=False + queryset=Facility.objects.all(), + allow_null=True, + required=False, ) assigned_facility_external = serializers.CharField( - required=False, allow_null=True, allow_blank=True + required=False, + allow_null=True, + allow_blank=True, ) assigned_facility_object = FacilityBasicInfoSerializer( - source="assigned_facility", read_only=True + source="assigned_facility", + read_only=True, ) assigned_facility_type = ChoiceField( - choices=FACILITY_TYPES, required=False, allow_null=True + choices=FACILITY_TYPES, + required=False, + allow_null=True, ) preferred_vehicle_choice = ChoiceField( - choices=VEHICLE_CHOICES, required=False, allow_null=True + choices=VEHICLE_CHOICES, + required=False, + allow_null=True, ) assigned_to_object = UserBaseMinimumSerializer(source="assigned_to", read_only=True) created_by_object = UserBaseMinimumSerializer(source="created_by", read_only=True) last_edited_by_object = UserBaseMinimumSerializer( - source="last_edited_by", read_only=True + source="last_edited_by", + read_only=True, ) patient_category = ChoiceField(choices=CATEGORY_CHOICES, required=False) ambulance_driver_name = serializers.CharField( - required=False, allow_null=True, allow_blank=True + required=False, + allow_null=True, + allow_blank=True, ) ambulance_number = serializers.CharField( - required=False, allow_null=True, allow_blank=True + required=False, + allow_null=True, + allow_blank=True, ) def __init__(self, instance=None, **kwargs): @@ -237,10 +259,10 @@ def validate_shifting_approving_facility(self, value): raise ValidationError("Shifting Approving Facility is required") return value - def update(self, instance, validated_data): + def update(self, instance, validated_data): # noqa: PLR0912 if instance.status == REVERSE_SHIFTING_STATUS_CHOICES["CANCELLED"]: raise ValidationError("Permission Denied, Shifting request was cancelled.") - elif instance.status == REVERSE_SHIFTING_STATUS_CHOICES["COMPLETED"]: + if instance.status == REVERSE_SHIFTING_STATUS_CHOICES["COMPLETED"]: raise ValidationError("Permission Denied, Shifting request was completed.") # Dont allow editing origin or patient @@ -274,9 +296,7 @@ def update(self, instance, validated_data): if ( status in self.PEACETIME_SHIFTING_STATUS and has_facility_permission(user, instance.origin_facility) - ): - pass - elif ( + ) or ( status in self.PEACETIME_RECIEVING_STATUS and has_facility_permission(user, instance.assigned_facility) ): @@ -288,17 +308,18 @@ def update(self, instance, validated_data): status in self.LIMITED_RECIEVING_STATUS and instance.assigned_facility and not has_facility_permission(user, instance.assigned_facility) - ): - raise ValidationError({"status": ["Permission Denied"]}) - - elif status in self.LIMITED_SHIFTING_STATUS and not has_facility_permission( - user, instance.shifting_approving_facility + ) or ( + status in self.LIMITED_SHIFTING_STATUS + and not has_facility_permission( + user, + instance.shifting_approving_facility, + ) ): raise ValidationError({"status": ["Permission Denied"]}) assigned = bool( validated_data.get("assigned_facility") - or validated_data.get("assigned_facility_external") + or validated_data.get("assigned_facility_external"), ) if ( @@ -312,9 +333,9 @@ def update(self, instance, validated_data): raise ValidationError( { "status": [ - "Destination Facility is required for moving to this stage." - ] - } + "Destination Facility is required for moving to this stage.", + ], + }, ) validated_data["last_edited_by"] = self.context["request"].user @@ -337,7 +358,8 @@ def update(self, instance, validated_data): if ( "status" in validated_data and validated_data["status"] != old_status - and validated_data["status"] == 40 + and validated_data["status"] + == REVERSE_SHIFTING_STATUS_CHOICES["DESTINATION APPROVED"] ): NotificationGenerator( event=Notification.Event.SHIFTING_UPDATED, @@ -363,7 +385,7 @@ def create(self, validated_data): assigned = bool( validated_data.get("assigned_facility") - or validated_data.get("assigned_facility_external") + or validated_data.get("assigned_facility_external"), ) if ( "status" in validated_data @@ -373,19 +395,30 @@ def create(self, validated_data): raise ValidationError( { "status": [ - "Destination Facility is required for moving to this stage." - ] - } + "Destination Facility is required for moving to this stage.", + ], + }, ) validated_data["is_kasp"] = False patient = validated_data["patient"] if ShiftingRequest.objects.filter( - ~Q(status__in=[30, 50, 80, 100]), patient=patient + ~Q( + status__in=[ + REVERSE_SHIFTING_STATUS_CHOICES[x] + for x in [ + "REJECTED", + "DESTINATION REJECTED", + "PATIENT EXPIRED", + "CANCELLED", + ] + ], + ), + patient=patient, ).exists(): raise ValidationError( - {"request": ["Shifting Request for Patient already exists"]} + {"request": ["Shifting Request for Patient already exists"]}, ) if not patient.is_active: diff --git a/care/facility/api/viewsets/ambulance.py b/care/facility/api/viewsets/ambulance.py index f67e3457a4..95b0c73515 100644 --- a/care/facility/api/viewsets/ambulance.py +++ b/care/facility/api/viewsets/ambulance.py @@ -30,13 +30,16 @@ class AmbulanceFilterSet(filters.FilterSet): third_district = filters.CharFilter(field_name="third_district_id") primary_district_name = filters.CharFilter( - field_name="primary_district__name", lookup_expr="icontains" + field_name="primary_district__name", + lookup_expr="icontains", ) secondary_district_name = filters.CharFilter( - field_name="secondary_district__name", lookup_expr="icontains" + field_name="secondary_district__name", + lookup_expr="icontains", ) third_district_name = filters.CharFilter( - field_name="third_district__name", lookup_expr="icontains" + field_name="third_district__name", + lookup_expr="icontains", ) @@ -51,7 +54,9 @@ class AmbulanceViewSet( permission_classes = (IsAuthenticated,) serializer_class = AmbulanceSerializer queryset = Ambulance.objects.filter(deleted=False).select_related( - "primary_district", "secondary_district", "third_district" + "primary_district", + "secondary_district", + "third_district", ) filter_backends = (filters.DjangoFilterBackend,) filterset_class = AmbulanceFilterSet @@ -59,7 +64,7 @@ class AmbulanceViewSet( def get_serializer_class(self): if self.action == "add_driver": return AmbulanceDriverSerializer - elif self.action == "remove_driver": + if self.action == "remove_driver": return DeleteDriverSerializer return AmbulanceSerializer @@ -84,7 +89,7 @@ def remove_driver(self, request): serializer.is_valid(raise_exception=True) driver = ambulance.ambulancedriver_set.filter( - id=serializer.validated_data["driver_id"] + id=serializer.validated_data["driver_id"], ).first() if not driver: raise serializers.ValidationError({"driver_id": "Detail not found"}) diff --git a/care/facility/api/viewsets/asset.py b/care/facility/api/viewsets/asset.py index be7c203ebd..7f4bd83836 100644 --- a/care/facility/api/viewsets/asset.py +++ b/care/facility/api/viewsets/asset.py @@ -9,9 +9,8 @@ from djqscsv import render_to_csv_response from drf_spectacular.utils import extend_schema, inline_serializer from dry_rest_permissions.generics import DRYPermissions -from rest_framework import exceptions +from rest_framework import exceptions, status from rest_framework import filters as drf_filters -from rest_framework import status from rest_framework.decorators import action from rest_framework.exceptions import APIException, ValidationError from rest_framework.mixins import ( @@ -97,13 +96,13 @@ def get_queryset(self): queryset = queryset.filter(facility__id__in=allowed_facilities) return queryset.filter( - facility__external_id=self.kwargs["facility_external_id"] + facility__external_id=self.kwargs["facility_external_id"], ) def get_facility(self): facilities = get_facility_queryset(self.request.user) return get_object_or_404( - facilities.filter(external_id=self.kwargs["facility_external_id"]) + facilities.filter(external_id=self.kwargs["facility_external_id"]), ) def perform_create(self, serializer): @@ -119,7 +118,7 @@ class AssetFilter(filters.FilterSet): is_working = filters.BooleanFilter() qr_code_id = filters.CharFilter(field_name="qr_code_id", lookup_expr="icontains") in_use_by_consultation = filters.BooleanFilter( - method="filter_in_use_by_consultation" + method="filter_in_use_by_consultation", ) is_permanent = filters.BooleanFilter(method="filter_is_permanent") @@ -131,8 +130,8 @@ def filter_in_use_by_consultation(self, queryset, _, value): Q(consultation_bed__end_date__gt=timezone.now()) | Q(consultation_bed__end_date__isnull=True), asset=OuterRef("pk"), - ) - ) + ), + ), ) queryset = queryset.filter(is_in_use=value) return queryset.distinct() @@ -144,14 +143,14 @@ def filter_is_permanent(self, queryset, _, value): asset_class__in=[ AssetClasses.ONVIF.name, AssetClasses.HL7MONITOR.name, - ] + ], ) else: queryset = queryset.exclude( asset_class__in=[ AssetClasses.ONVIF.name, AssetClasses.HL7MONITOR.name, - ] + ], ) return queryset.distinct() @@ -168,7 +167,9 @@ def retrieve(self, request, *args, **kwargs): instance = self.get_object() serializer = self.get_serializer(instance) cache.set( - key, serializer.data, 60 * 60 * 24 + key, + serializer.data, + 60 * 60 * 24, ) # Cache the asset details for 24 hours return Response(serializer.data) return Response(hit) @@ -214,12 +215,12 @@ def get_queryset(self): queryset = queryset.filter(current_location__facility__state=user.state) elif user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: queryset = queryset.filter( - current_location__facility__district=user.district + current_location__facility__district=user.district, ) else: allowed_facilities = get_accessible_facilities(user) queryset = queryset.filter( - current_location__facility__id__in=allowed_facilities + current_location__facility__id__in=allowed_facilities, ) return queryset @@ -229,33 +230,34 @@ def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()).values(*mapping.keys()) pretty_mapping = Asset.CSV_MAKE_PRETTY.copy() return render_to_csv_response( - queryset, field_header_map=mapping, field_serializer_map=pretty_mapping + queryset, field_header_map=mapping, field_serializer_map=pretty_mapping, ) - return super(AssetViewSet, self).list(request, *args, **kwargs) + return super().list(request, *args, **kwargs) def destroy(self, request, *args, **kwargs): user = self.request.user if user.user_type >= User.TYPE_VALUE_MAP["DistrictAdmin"]: return super().destroy(request, *args, **kwargs) - else: - raise exceptions.AuthenticationFailed( - "Only District Admin and above can delete assets" - ) + raise exceptions.AuthenticationFailed( + "Only District Admin and above can delete assets", + ) @extend_schema( - responses={200: UserDefaultAssetLocationSerializer()}, tags=["asset"] + responses={200: UserDefaultAssetLocationSerializer()}, + tags=["asset"], ) @action(detail=False, methods=["GET"]) def get_default_user_location(self, request, *args, **kwargs): obj = get_object_or_404( - UserDefaultAssetLocation.objects.filter(user=request.user) + UserDefaultAssetLocation.objects.filter(user=request.user), ) return Response(UserDefaultAssetLocationSerializer(obj).data) @extend_schema( request=inline_serializer( - "AssetLocationFieldSerializer", fields={"location": UUIDField()} + "AssetLocationFieldSerializer", + fields={"location": UUIDField()}, ), responses={200: UserDefaultAssetLocationSerializer()}, tags=["asset"], @@ -294,7 +296,7 @@ def operate_assets(self, request, *args, **kwargs): { **asset.meta, "middleware_hostname": asset.current_location.facility.middleware_address, - } + }, ) result = asset_class.handle_action(action) return Response({"result": result}, status=status.HTTP_200_OK) @@ -311,8 +313,7 @@ def operate_assets(self, request, *args, **kwargs): except APIException as e: return Response(e.detail, e.status_code) - except Exception as e: - print(f"error: {e}") + except Exception: return Response( {"message": "Internal Server Error"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -349,18 +350,18 @@ def get_queryset(self): elif user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: queryset = queryset.filter( Q(from_location__facility__state=user.state) - | Q(to_location__facility__state=user.state) + | Q(to_location__facility__state=user.state), ) elif user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: queryset = queryset.filter( Q(from_location__facility__district=user.district) - | Q(to_location__facility__district=user.district) + | Q(to_location__facility__district=user.district), ) else: allowed_facilities = get_accessible_facilities(user) queryset = queryset.filter( Q(from_location__facility__id__in=allowed_facilities) - | Q(to_location__facility__id__in=allowed_facilities) + | Q(to_location__facility__id__in=allowed_facilities), ) return queryset @@ -396,21 +397,21 @@ class AssetServiceViewSet( def get_queryset(self): user = self.request.user queryset = self.queryset.filter( - asset__external_id=self.kwargs.get("asset_external_id") + asset__external_id=self.kwargs.get("asset_external_id"), ) if user.is_superuser: pass elif user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: queryset = queryset.filter( - asset__current_location__facility__state=user.state + asset__current_location__facility__state=user.state, ) elif user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: queryset = queryset.filter( - asset__current_location__facility__district=user.district + asset__current_location__facility__district=user.district, ) else: allowed_facilities = get_accessible_facilities(user) queryset = queryset.filter( - asset__current_location__facility__id__in=allowed_facilities + asset__current_location__facility__id__in=allowed_facilities, ) return queryset diff --git a/care/facility/api/viewsets/bed.py b/care/facility/api/viewsets/bed.py index 263f6e7627..e2e10dcba1 100644 --- a/care/facility/api/viewsets/bed.py +++ b/care/facility/api/viewsets/bed.py @@ -80,7 +80,9 @@ def create(self, request, *args, **kwargs): self.perform_create(serializer) headers = self.get_success_headers(serializer.data) return Response( - serializer.data, status=status.HTTP_201_CREATED, headers=headers + serializer.data, + status=status.HTTP_201_CREATED, + headers=headers, ) def get_queryset(self): @@ -103,7 +105,7 @@ def destroy(self, request, *args, **kwargs): instance = self.get_object() if instance.is_occupied: raise DRFValidationError( - detail="Bed is occupied. Please discharge the patient first" + detail="Bed is occupied. Please discharge the patient first", ) return super().destroy(request, *args, **kwargs) @@ -159,9 +161,10 @@ def filter_bed_is_occupied(self, queryset, name, value): return queryset.filter( bed__id__in=Subquery( ConsultationBed.objects.filter( - bed__id=OuterRef("bed__id"), end_date__isnull=value - ).values("bed__id") - ) + bed__id=OuterRef("bed__id"), + end_date__isnull=value, + ).values("bed__id"), + ), ) @@ -192,7 +195,7 @@ def get_queryset(self): allowed_facilities = get_accessible_facilities(user) queryset = queryset.filter(bed__facility__id__in=allowed_facilities) return queryset.filter( - bed__facility__external_id=self.kwargs["facility_external_id"] + bed__facility__external_id=self.kwargs["facility_external_id"], ) diff --git a/care/facility/api/viewsets/daily_round.py b/care/facility/api/viewsets/daily_round.py index 387c5489a3..81ec213c6a 100644 --- a/care/facility/api/viewsets/daily_round.py +++ b/care/facility/api/viewsets/daily_round.py @@ -1,3 +1,5 @@ +import contextlib + from django_filters import rest_framework as filters from drf_spectacular.utils import extend_schema from dry_rest_permissions.generics import DRYPermissions @@ -25,10 +27,9 @@ def filter_rounds_type(self, queryset, name, value): rounds_type = set() values = value.split(",") for v in values: - try: + with contextlib.suppress(KeyError): rounds_type.add(DailyRound.RoundsTypeDict[v]) - except KeyError: - pass + return queryset.filter(rounds_type__in=list(rounds_type)) @@ -57,13 +58,13 @@ class DailyRoundsViewSet( def get_queryset(self): return self.queryset.filter( - consultation__external_id=self.kwargs["consultation_external_id"] + consultation__external_id=self.kwargs["consultation_external_id"], ) def get_serializer(self, *args, **kwargs): if "data" in kwargs: kwargs["data"]["consultation"] = PatientConsultation.objects.get( - external_id=self.kwargs["consultation_external_id"] + external_id=self.kwargs["consultation_external_id"], ).id return super().get_serializer(*args, **kwargs) @@ -101,11 +102,11 @@ def analyse(self, request, **kwargs): consultation = get_object_or_404( get_consultation_queryset(request.user).filter( - external_id=self.kwargs["consultation_external_id"] - ) + external_id=self.kwargs["consultation_external_id"], + ), ) daily_round_objects = DailyRound.objects.filter( - consultation=consultation + consultation=consultation, ).order_by("-taken_at") total_count = daily_round_objects.count() daily_round_objects = daily_round_objects[ diff --git a/care/facility/api/viewsets/facility.py b/care/facility/api/viewsets/facility.py index 8e4f4bd3f7..4beac01e56 100644 --- a/care/facility/api/viewsets/facility.py +++ b/care/facility/api/viewsets/facility.py @@ -30,11 +30,13 @@ class FacilityFilter(filters.FilterSet): facility_type = filters.NumberFilter(field_name="facility_type") district = filters.NumberFilter(field_name="district__id") district_name = filters.CharFilter( - field_name="district__name", lookup_expr="icontains" + field_name="district__name", + lookup_expr="icontains", ) local_body = filters.NumberFilter(field_name="local_body__id") local_body_name = filters.CharFilter( - field_name="local_body__name", lookup_expr="icontains" + field_name="local_body__name", + lookup_expr="icontains", ) state = filters.NumberFilter(field_name="state__id") state_name = filters.CharFilter(field_name="state__name", lookup_expr="icontains") @@ -66,7 +68,10 @@ class FacilityViewSet( """Viewset for facility CRUD operations.""" queryset = Facility.objects.all().select_related( - "ward", "local_body", "district", "state" + "ward", + "local_body", + "district", + "state", ) permission_classes = ( IsAuthenticated, @@ -101,8 +106,7 @@ def get_serializer_class(self): if self.action == "cover_image": # Check DRYpermissions before updating return FacilityImageUploadSerializer - else: - return FacilitySerializer + return FacilitySerializer def destroy(self, request, *args, **kwargs): if ( @@ -110,14 +114,14 @@ def destroy(self, request, *args, **kwargs): or request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] ): if not PatientRegistration.objects.filter( - facility=self.get_object(), is_active=True + facility=self.get_object(), + is_active=True, ).exists(): return super().destroy(request, *args, **kwargs) - else: - return Response( - {"facility": "cannot delete facility with active patients"}, - status=status.HTTP_400_BAD_REQUEST, - ) + return Response( + {"facility": "cannot delete facility with active patients"}, + status=status.HTTP_400_BAD_REQUEST, + ) return Response({"permission": "denied"}, status=status.HTTP_403_FORBIDDEN) def list(self, request, *args, **kwargs): @@ -133,14 +137,16 @@ def list(self, request, *args, **kwargs): elif self.FACILITY_TRIAGE_CSV_KEY in request.GET: mapping.update(FacilityPatientStatsHistory.CSV_RELATED_MAPPING.copy()) pretty_mapping.update( - FacilityPatientStatsHistory.CSV_MAKE_PRETTY.copy() + FacilityPatientStatsHistory.CSV_MAKE_PRETTY.copy(), ) queryset = self.filter_queryset(self.get_queryset()).values(*mapping.keys()) return render_to_csv_response( - queryset, field_header_map=mapping, field_serializer_map=pretty_mapping + queryset, + field_header_map=mapping, + field_serializer_map=pretty_mapping, ) - return super(FacilityViewSet, self).list(request, *args, **kwargs) + return super().list(request, *args, **kwargs) @extend_schema(tags=["facility"]) @action(methods=["POST"], detail=True) diff --git a/care/facility/api/viewsets/facility_capacity.py b/care/facility/api/viewsets/facility_capacity.py index cfb92ef8b7..4128f8ebff 100644 --- a/care/facility/api/viewsets/facility_capacity.py +++ b/care/facility/api/viewsets/facility_capacity.py @@ -26,24 +26,25 @@ class FacilityCapacityViewSet(FacilityBaseViewset, ListModelMixin): def get_queryset(self): user = self.request.user queryset = self.queryset.filter( - facility__external_id=self.kwargs.get("facility_external_id") + facility__external_id=self.kwargs.get("facility_external_id"), ) if user.is_superuser: return queryset - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: return queryset.filter(facility__state=user.state) - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: return queryset.filter(facility__district=user.district) return queryset.filter(facility__users__id__exact=user.id) def get_object(self): return get_object_or_404( - self.get_queryset(), room_type=self.kwargs.get("external_id") + self.get_queryset(), + room_type=self.kwargs.get("external_id"), ) def get_facility(self): facility_qs = Facility.objects.filter( - external_id=self.kwargs.get("facility_external_id") + external_id=self.kwargs.get("facility_external_id"), ) if not self.request.user.is_superuser: facility_qs.filter(users__id__exact=self.request.user.id) diff --git a/care/facility/api/viewsets/facility_users.py b/care/facility/api/viewsets/facility_users.py index 578f326849..9c00e06ca0 100644 --- a/care/facility/api/viewsets/facility_users.py +++ b/care/facility/api/viewsets/facility_users.py @@ -50,4 +50,4 @@ def get_queryset(self): ), ) except Facility.DoesNotExist: - raise ValidationError({"Facility": "Facility not found"}) + raise ValidationError({"Facility": "Facility not found"}) from None diff --git a/care/facility/api/viewsets/file_upload.py b/care/facility/api/viewsets/file_upload.py index 5a238e476c..20f8276b6f 100644 --- a/care/facility/api/viewsets/file_upload.py +++ b/care/facility/api/viewsets/file_upload.py @@ -44,19 +44,18 @@ class FileUploadViewSet( def get_serializer_class(self): if self.action == "retrieve": return FileUploadRetrieveSerializer - elif self.action == "list": + if self.action == "list": return FileUploadListSerializer - elif self.action == "create": + if self.action == "create": return FileUploadCreateSerializer - else: - return FileUploadUpdateSerializer + return FileUploadUpdateSerializer def get_queryset(self): if "file_type" not in self.request.GET: raise ValidationError({"file_type": "file_type missing in request params"}) if "associating_id" not in self.request.GET: raise ValidationError( - {"associating_id": "associating_id missing in request params"} + {"associating_id": "associating_id missing in request params"}, ) file_type = self.request.GET["file_type"] associating_id = self.request.GET["associating_id"] @@ -64,8 +63,12 @@ def get_queryset(self): raise ValidationError({"file_type": "invalid file type"}) file_type = FileUpload.FileType[file_type].value associating_internal_id = check_permissions( - file_type, associating_id, self.request.user, "read" + file_type, + associating_id, + self.request.user, + "read", ) return self.queryset.filter( - file_type=file_type, associating_id=associating_internal_id + file_type=file_type, + associating_id=associating_internal_id, ) diff --git a/care/facility/api/viewsets/hospital_doctor.py b/care/facility/api/viewsets/hospital_doctor.py index c3d14c258d..bad33e97ca 100644 --- a/care/facility/api/viewsets/hospital_doctor.py +++ b/care/facility/api/viewsets/hospital_doctor.py @@ -21,13 +21,13 @@ class HospitalDoctorViewSet(FacilityBaseViewset, ListModelMixin): def get_queryset(self): user = self.request.user queryset = self.queryset.filter( - facility__external_id=self.kwargs.get("facility_external_id") + facility__external_id=self.kwargs.get("facility_external_id"), ) if user.is_superuser: return queryset - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: return queryset.filter(facility__state=user.state) - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: return queryset.filter(facility__district=user.district) return queryset.filter(facility__users__id__exact=user.id) @@ -36,7 +36,7 @@ def get_object(self): def get_facility(self): facility_qs = Facility.objects.filter( - external_id=self.kwargs.get("facility_external_id") + external_id=self.kwargs.get("facility_external_id"), ) if not self.request.user.is_superuser: facility_qs.filter(users__id__exact=self.request.user.id) diff --git a/care/facility/api/viewsets/icd.py b/care/facility/api/viewsets/icd.py index 2b5851d05c..76e46b6947 100644 --- a/care/facility/api/viewsets/icd.py +++ b/care/facility/api/viewsets/icd.py @@ -7,10 +7,9 @@ def serailize_data(icd11_object): result = [] - for object in icd11_object: - if type(object) == tuple: - object = object[0] - result.append({"id": object.id, "label": object.label}) + for _obj in icd11_object: + obj = _obj[0] if isinstance(_obj, tuple) else _obj + result.append({"id": obj.id, "label": obj.label}) return result @@ -24,6 +23,6 @@ def list(self, request): if request.GET.get("query", False): query = request.GET.get("query") queryset = queryset.where( - label=queryset.re_match(r".*" + query + r".*", IGNORECASE) + label=queryset.re_match(r".*" + query + r".*", IGNORECASE), ) # can accept regex from FE if needed. return Response(serailize_data(queryset[0:100])) diff --git a/care/facility/api/viewsets/inventory.py b/care/facility/api/viewsets/inventory.py index 3453c6c3b4..b266763c31 100644 --- a/care/facility/api/viewsets/inventory.py +++ b/care/facility/api/viewsets/inventory.py @@ -33,7 +33,7 @@ ) from care.users.models import User from care.utils.queryset.facility import get_facility_queryset -from care.utils.validation.integer_validation import check_integer +from care.utils.validators.integer_validator import check_integer class FacilityInventoryFilter(filters.FilterSet): @@ -72,7 +72,7 @@ class FacilityInventoryLogViewSet( lookup_field = "external_id" serializer_class = FacilityInventoryLogSerializer queryset = FacilityInventoryLog.objects.select_related("item", "unit").order_by( - "-created_date" + "-created_date", ) permission_classes = ( IsAuthenticated, @@ -84,32 +84,34 @@ class FacilityInventoryLogViewSet( def get_queryset(self): user = self.request.user queryset = self.queryset.filter( - facility__external_id=self.kwargs.get("facility_external_id") + facility__external_id=self.kwargs.get("facility_external_id"), ) if user.is_superuser: return queryset - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: return queryset.filter(facility__state=user.state) - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: return queryset.filter(facility__district=user.district) return queryset.filter(facility__users__id__exact=user.id) def get_object(self): return get_object_or_404( - self.get_queryset(), external_id=self.kwargs.get("external_id") + self.get_queryset(), + external_id=self.kwargs.get("external_id"), ) def get_facility(self): queryset = get_facility_queryset(self.request.user) return get_object_or_404( - queryset.filter(external_id=self.kwargs.get("facility_external_id")) + queryset.filter(external_id=self.kwargs.get("facility_external_id")), ) @extend_schema(tags=["inventory"]) @action(methods=["PUT"], detail=True) def flag(self, request, **kwargs): log_obj = get_object_or_404( - self.get_queryset(), external_id=self.kwargs.get("external_id") + self.get_queryset(), + external_id=self.kwargs.get("external_id"), ) log_obj.probable_accident = not log_obj.probable_accident log_obj.save() @@ -126,7 +128,8 @@ def delete_last(self, request, **kwargs): item = check_integer(item)[0] item_obj = get_object_or_404(FacilityInventoryItem.objects.filter(id=item)) inventory_log_object = FacilityInventoryLog.objects.filter( - item=item_obj, facility=facility + item=item_obj, + facility=facility, ).order_by("-id") if not inventory_log_object.exists(): raise ValidationError({"inventory": "Does not Exist"}) @@ -168,24 +171,25 @@ class FacilityInventoryMinQuantityViewSet( def get_queryset(self): user = self.request.user queryset = self.queryset.filter( - facility__external_id=self.kwargs.get("facility_external_id") + facility__external_id=self.kwargs.get("facility_external_id"), ) if user.is_superuser: return queryset - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: return queryset.filter(facility__state=user.state) - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: return queryset.filter(facility__district=user.district) return queryset.filter(facility__users__id__exact=user.id) def get_object(self): return get_object_or_404( - self.get_queryset(), external_id=self.kwargs.get("external_id") + self.get_queryset(), + external_id=self.kwargs.get("external_id"), ) def get_facility(self): facility_qs = Facility.objects.filter( - external_id=self.kwargs.get("facility_external_id") + external_id=self.kwargs.get("facility_external_id"), ) if not self.request.user.is_superuser: facility_qs.filter(users__id__exact=self.request.user.id) @@ -212,40 +216,18 @@ class FacilityInventorySummaryViewSet( def get_queryset(self): user = self.request.user queryset = self.queryset.filter( - facility__external_id=self.kwargs.get("facility_external_id") + facility__external_id=self.kwargs.get("facility_external_id"), ) if user.is_superuser: return queryset - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: return queryset.filter(facility__state=user.state) - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: return queryset.filter(facility__district=user.district) return queryset.filter(facility__users__id__exact=user.id) def get_object(self): return get_object_or_404( - self.get_queryset(), external_id=self.kwargs.get("external_id") + self.get_queryset(), + external_id=self.kwargs.get("external_id"), ) - - -# class FacilityInventoryBurnRateFilter(filters.FilterSet): -# name = filters.CharFilter(field_name="facility__name", lookup_expr="icontains") -# item = filters.NumberFilter(field_name="item_id") - - -# class FacilityInventoryBurnRateViewSet( -# UserAccessMixin, ListModelMixin, RetrieveModelMixin, GenericViewSet, -# ): -# queryset = FacilityInventoryBurnRate.objects.select_related( -# "item", "item__default_unit", "facility__district" -# ).all() -# filter_backends = (filters.DjangoFilterBackend,) -# filterset_class = FacilityInventoryBurnRateFilter -# permission_classes = (IsAuthenticated, DRYPermissions) -# serializer_class = FacilityInventoryBurnRateSerializer - -# def filter_queryset(self, queryset): -# queryset = super().filter_queryset(queryset) -# if self.kwargs.get("facility_external_id"): -# queryset = queryset.filter(facility__external_id=self.kwargs.get("facility_external_id")) -# return self.filter_by_user_scope(queryset) diff --git a/care/facility/api/viewsets/mixins/access.py b/care/facility/api/viewsets/mixins/access.py index 28cebe4314..ecaf53c0fb 100644 --- a/care/facility/api/viewsets/mixins/access.py +++ b/care/facility/api/viewsets/mixins/access.py @@ -15,11 +15,10 @@ def get_queryset(self): queryset = queryset.filter(district=self.request.user.district) if hasattr(instance, "facility"): queryset = queryset.filter( - facility__district=self.request.user.district + facility__district=self.request.user.district, ) - else: - if hasattr(instance, "created_by"): - queryset = queryset.filter(created_by=self.request.user) + elif hasattr(instance, "created_by"): + queryset = queryset.filter(created_by=self.request.user) return queryset def filter_by_user_scope(self, queryset): @@ -32,11 +31,10 @@ def filter_by_user_scope(self, queryset): queryset = queryset.filter(district=self.request.user.district) if hasattr(instance, "facility_id"): queryset = queryset.filter( - facility__district=self.request.user.district + facility__district=self.request.user.district, ) - else: - if hasattr(instance, "created_by"): - queryset = queryset.filter(created_by=self.request.user) + elif hasattr(instance, "created_by"): + queryset = queryset.filter(created_by=self.request.user) return queryset def perform_create(self, serializer): @@ -64,7 +62,7 @@ def get_permissions(self): if bool( self.request.user and self.request.user.is_authenticated - and self.request.user.asset + and self.request.user.asset, ): return tuple(permission() for permission in self.asset_permissions) return super().get_permissions() diff --git a/care/facility/api/viewsets/notification.py b/care/facility/api/viewsets/notification.py index 05f42ff6e0..4718fc2ad2 100644 --- a/care/facility/api/viewsets/notification.py +++ b/care/facility/api/viewsets/notification.py @@ -29,7 +29,10 @@ class NotificationFilter(filters.FilterSet): class NotificationViewSet( - RetrieveModelMixin, ListModelMixin, UpdateModelMixin, GenericViewSet + RetrieveModelMixin, + ListModelMixin, + UpdateModelMixin, + GenericViewSet, ): queryset = ( Notification.objects.all() @@ -48,7 +51,9 @@ def get_queryset(self): @extend_schema(tags=["notification"]) @action( - detail=False, methods=["GET"], permission_classes=[IsAuthenticatedOrReadOnly] + detail=False, + methods=["GET"], + permission_classes=[IsAuthenticatedOrReadOnly], ) def public_key(self, request, *args, **kwargs): return Response({"public_key": settings.VAPID_PUBLIC_KEY}) @@ -73,7 +78,7 @@ def notify(self, request, *args, **kwargs): raise ValidationError({"message": "is required"}) facilities = get_facility_queryset(user) facility = get_object_or_404( - facilities.filter(external_id=request.data["facility"]) + facilities.filter(external_id=request.data["facility"]), ) NotificationGenerator( event_type=Notification.EventType.CUSTOM_MESSAGE, diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index 0a502dc06a..e50ffb6c94 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -1,4 +1,3 @@ -import datetime import json from json import JSONDecodeError @@ -7,6 +6,7 @@ from django.db import models from django.db.models import Case, When from django.db.models.query_utils import Q +from django.utils import timezone from django_filters import rest_framework as filters from djqscsv import render_to_csv_response from drf_spectacular.utils import extend_schema, extend_schema_view @@ -79,7 +79,8 @@ class PatientFilterSet(filters.FilterSet): allow_transfer = filters.BooleanFilter(field_name="allow_transfer") name = filters.CharFilter(field_name="name", lookup_expr="icontains") patient_no = filters.CharFilter( - field_name="last_consultation__patient_no", lookup_expr="icontains" + field_name="last_consultation__patient_no", + lookup_expr="icontains", ) gender = filters.NumberFilter(field_name="gender") age = filters.NumberFilter(field_name="age") @@ -104,7 +105,7 @@ def filter_by_category(self, queryset, name, value): | ( Q(last_consultation__last_daily_round__isnull=True) & Q(last_consultation__category=value) - ) + ), ) return queryset @@ -113,38 +114,40 @@ def filter_by_category(self, queryset, name, value): srf_id = filters.CharFilter(field_name="srf_id") is_declared_positive = filters.BooleanFilter(field_name="is_declared_positive") date_declared_positive = filters.DateFromToRangeFilter( - field_name="date_declared_positive" + field_name="date_declared_positive", ) date_of_result = filters.DateFromToRangeFilter(field_name="date_of_result") last_vaccinated_date = filters.DateFromToRangeFilter( - field_name="last_vaccinated_date" + field_name="last_vaccinated_date", ) is_antenatal = filters.BooleanFilter(field_name="is_antenatal") is_active = filters.BooleanFilter(field_name="is_active") # Location Based Filtering district = filters.NumberFilter(field_name="district__id") district_name = filters.CharFilter( - field_name="district__name", lookup_expr="icontains" + field_name="district__name", + lookup_expr="icontains", ) local_body = filters.NumberFilter(field_name="local_body__id") local_body_name = filters.CharFilter( - field_name="local_body__name", lookup_expr="icontains" + field_name="local_body__name", + lookup_expr="icontains", ) state = filters.NumberFilter(field_name="state__id") state_name = filters.CharFilter(field_name="state__name", lookup_expr="icontains") # Consultation Fields is_kasp = filters.BooleanFilter(field_name="last_consultation__is_kasp") last_consultation_kasp_enabled_date = filters.DateFromToRangeFilter( - field_name="last_consultation__kasp_enabled_date" + field_name="last_consultation__kasp_enabled_date", ) last_consultation_admission_date = filters.DateFromToRangeFilter( - field_name="last_consultation__admission_date" + field_name="last_consultation__admission_date", ) last_consultation_discharge_date = filters.DateFromToRangeFilter( - field_name="last_consultation__discharge_date" + field_name="last_consultation__discharge_date", ) last_consultation_symptoms_onset_date = filters.DateFromToRangeFilter( - field_name="last_consultation__symptoms_onset_date" + field_name="last_consultation__symptoms_onset_date", ) last_consultation_admitted_bed_type_list = MultiSelectFilter( method="filter_by_bed_type", @@ -174,10 +177,10 @@ def filter_by_bed_type(self, queryset, name, value): choices=DISCHARGE_REASON_CHOICES, ) last_consultation_assigned_to = filters.NumberFilter( - field_name="last_consultation__assigned_to" + field_name="last_consultation__assigned_to", ) last_consultation_is_telemedicine = filters.BooleanFilter( - field_name="last_consultation__is_telemedicine" + field_name="last_consultation__is_telemedicine", ) ventilator_interface = CareChoiceFilter( field_name="last_consultation__last_daily_round__ventilator_interface", @@ -207,7 +210,7 @@ def filter_queryset(self, request, queryset, view): if request.user.asset: return queryset.filter( last_consultation__last_daily_round__bed_id__in=AssetBed.objects.filter( - asset=request.user.asset + asset=request.user.asset, ).values("id"), last_consultation__last_daily_round__bed__isnull=False, ) @@ -228,7 +231,7 @@ def filter_queryset(self, request, queryset, view): def filter_list_queryset(self, request, queryset, view): try: show_without_facility = json.loads( - request.query_params.get("without_facility") + request.query_params.get("without_facility"), ) except ( JSONDecodeError, @@ -242,7 +245,7 @@ class PatientCustomOrderingFilter(BaseFilterBackend): def filter_queryset(self, request, queryset, view): ordering = request.query_params.get("ordering", "") - if ordering == "category_severity" or ordering == "-category_severity": + if ordering in {"category_severity", "-category_severity"}: category_ordering = { category: index + 1 for index, (category, _) in enumerate(CATEGORY_CHOICES) @@ -256,7 +259,7 @@ def filter_queryset(self, request, queryset, view): *when_statements, default=(len(category_ordering) + 1), output_field=models.IntegerField(), - ) + ), ).order_by(ordering) return queryset @@ -330,26 +333,14 @@ class PatientViewSet( ] CSV_EXPORT_LIMIT = 7 - def get_queryset(self): - # filter_query = self.request.query_params.get("disease_status") - queryset = super().get_queryset() - # if filter_query: - # disease_status = filter_query if filter_query.isdigit() else DiseaseStatusEnum[filter_query].value - # return queryset.filter(disease_status=disease_status) - - # if self.action == "list": - # queryset = queryset.filter(is_active=self.request.GET.get("is_active", True)) - return queryset - def get_serializer_class(self): if self.action == "list": return PatientListSerializer - elif self.action == "icmr_sample": + if self.action == "icmr_sample": return PatientICMRSerializer - elif self.action == "transfer": + if self.action == "transfer": return PatientTransferSerializer - else: - return self.serializer_class + return self.serializer_class def list(self, request, *args, **kwargs): """ @@ -371,7 +362,9 @@ def list(self, request, *args, **kwargs): if settings.CSV_REQUEST_PARAMETER in request.GET: # Start Date Validation temp = filters.DjangoFilterBackend().get_filterset( - self.request, self.queryset, self + self.request, + self.queryset, + self, ) temp.is_valid() within_limits = False @@ -381,8 +374,8 @@ def list(self, request, *args, **kwargs): if not slice_obj.start or not slice_obj.stop: raise ValidationError( { - field: "both starting and ending date must be provided for export" - } + field: "both starting and ending date must be provided for export", + }, ) days_difference = ( temp.form.cleaned_data.get(field).stop @@ -393,18 +386,18 @@ def list(self, request, *args, **kwargs): else: raise ValidationError( { - field: f"Cannot export more than {self.CSV_EXPORT_LIMIT} days at a time" - } + field: f"Cannot export more than {self.CSV_EXPORT_LIMIT} days at a time", + }, ) if not within_limits: raise ValidationError( { - "date": f"Atleast one date field must be filtered to be within {self.CSV_EXPORT_LIMIT} days" - } + "date": f"Atleast one date field must be filtered to be within {self.CSV_EXPORT_LIMIT} days", + }, ) # End Date Limiting Validation queryset = self.filter_queryset(self.get_queryset()).values( - *PatientRegistration.CSV_MAPPING.keys() + *PatientRegistration.CSV_MAPPING.keys(), ) return render_to_csv_response( queryset, @@ -412,7 +405,7 @@ def list(self, request, *args, **kwargs): field_serializer_map=PatientRegistration.CSV_MAKE_PRETTY, ) - return super(PatientViewSet, self).list(request, *args, **kwargs) + return super().list(request, *args, **kwargs) @extend_schema(tags=["patient"]) @action(detail=True, methods=["POST"]) @@ -435,7 +428,8 @@ def transfer(self, request, *args, **kwargs): # Update all Active Shifting Request to Rejected for shifting_request in ShiftingRequest.objects.filter( - ~Q(status__in=[30, 50, 80]), patient=patient + ~Q(status__in=[30, 50, 80]), + patient=patient, ): shifting_request.status = 30 shifting_request.comments = f"{shifting_request.comments}\n The shifting request was auto rejected by the system as the patient was moved to {patient.facility.name}" @@ -454,7 +448,7 @@ class FacilityPatientStatsHistoryViewSet(viewsets.ModelViewSet): DRYPermissions, ) queryset = FacilityPatientStatsHistory.objects.filter( - facility__deleted=False + facility__deleted=False, ).order_by("-entry_date") serializer_class = FacilityPatientStatsHistorySerializer filter_backends = (filters.DjangoFilterBackend,) @@ -464,24 +458,25 @@ class FacilityPatientStatsHistoryViewSet(viewsets.ModelViewSet): def get_queryset(self): user = self.request.user queryset = self.queryset.filter( - facility__external_id=self.kwargs.get("facility_external_id") + facility__external_id=self.kwargs.get("facility_external_id"), ) if user.is_superuser: return queryset - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: return queryset.filter(facility__state=user.state) - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: return queryset.filter(facility__district=user.district) return queryset.filter(facility__users__id__exact=user.id) def get_object(self): return get_object_or_404( - self.get_queryset(), external_id=self.kwargs.get("external_id") + self.get_queryset(), + external_id=self.kwargs.get("external_id"), ) def get_facility(self): facility_qs = Facility.objects.filter( - external_id=self.kwargs.get("facility_external_id") + external_id=self.kwargs.get("facility_external_id"), ) if not self.request.user.is_superuser: facility_qs.filter(users__id__exact=self.request.user.id) @@ -490,19 +485,6 @@ def get_facility(self): def perform_create(self, serializer): return serializer.save(facility=self.get_facility()) - def list(self, request, *args, **kwargs): - """ - Patient Stats - List - - Available Filters - - entry_date_before: date in YYYY-MM-DD format, inclusive of this date - - entry_date_before: date in YYYY-MM-DD format, inclusive of this date - - """ - return super(FacilityPatientStatsHistoryViewSet, self).list( - request, *args, **kwargs - ) - class PatientSearchSetPagination(PageNumberPagination): page_size = 200 @@ -527,62 +509,64 @@ class PatientSearchViewSet(ListModelMixin, GenericViewSet): def get_queryset(self): if self.action != "list": - return super(PatientSearchViewSet, self).get_queryset() + return super().get_queryset() + + # TODO: convert this to a filter set + serializer = PatientSearchSerializer( + data=self.request.query_params, + partial=True, + ) + serializer.is_valid(raise_exception=True) + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + search_keys = [ + "date_of_birth", + "year_of_birth", + "phone_number", + "name", + "age", + ] else: - serializer = PatientSearchSerializer( - data=self.request.query_params, partial=True + search_keys = [ + "date_of_birth", + "year_of_birth", + "phone_number", + "age", + ] + search_fields = { + key: serializer.validated_data[key] + for key in search_keys + if serializer.validated_data.get(key) + } + if not search_fields: + raise serializers.ValidationError( + { + "detail": [ + f"None of the search keys provided. Available: {', '.join(search_keys)}", + ], + }, ) - serializer.is_valid(raise_exception=True) - if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: - search_keys = [ - "date_of_birth", - "year_of_birth", - "phone_number", - "name", - "age", - ] - else: - search_keys = [ - "date_of_birth", - "year_of_birth", - "phone_number", - "age", - ] - search_fields = { - key: serializer.validated_data[key] - for key in search_keys - if serializer.validated_data.get(key) - } - if not search_fields: - raise serializers.ValidationError( - { - "detail": [ - f"None of the search keys provided. Available: {', '.join(search_keys)}" - ] - } - ) - # if not self.request.user.is_superuser: - # search_fields["state_id"] = self.request.user.state_id + # if not self.request.user.is_superuser: + # search_fields["state_id"] = self.request.user.state_id - if "age" in search_fields: - age = search_fields.pop("age") - year_of_birth = datetime.datetime.now().year - age - search_fields["age__gte"] = year_of_birth - 5 - search_fields["age__lte"] = year_of_birth + 5 + if "age" in search_fields: + age = search_fields.pop("age") + year_of_birth = timezone.now().year - age + search_fields["age__gte"] = year_of_birth - 5 + search_fields["age__lte"] = year_of_birth + 5 - name = search_fields.pop("name", None) + name = search_fields.pop("name", None) - queryset = self.queryset.filter(**search_fields) + queryset = self.queryset.filter(**search_fields) - if name: - queryset = ( - queryset.annotate(similarity=TrigramSimilarity("name", name)) - .filter(similarity__gt=0.2) - .order_by("-similarity") - ) + if name: + queryset = ( + queryset.annotate(similarity=TrigramSimilarity("name", name)) + .filter(similarity__gt=0.2) + .order_by("-similarity") + ) - return queryset + return queryset @extend_schema(tags=["patient"]) def list(self, request, *args, **kwargs): @@ -602,11 +586,14 @@ def list(self, request, *args, **kwargs): `Eg: api/v1/patient/search/?year_of_birth=1992&phone_number=%2B917795937091` """ - return super(PatientSearchViewSet, self).list(request, *args, **kwargs) + return super().list(request, *args, **kwargs) class PatientNotesViewSet( - ListModelMixin, RetrieveModelMixin, CreateModelMixin, GenericViewSet + ListModelMixin, + RetrieveModelMixin, + CreateModelMixin, + GenericViewSet, ): queryset = ( PatientNotes.objects.all() @@ -619,7 +606,7 @@ class PatientNotesViewSet( def get_queryset(self): user = self.request.user queryset = self.queryset.filter( - patient__external_id=self.kwargs.get("patient_external_id") + patient__external_id=self.kwargs.get("patient_external_id"), ) if not user.is_superuser: return queryset @@ -639,12 +626,12 @@ def get_queryset(self): def perform_create(self, serializer): patient = get_object_or_404( get_patient_notes_queryset(self.request.user).filter( - external_id=self.kwargs.get("patient_external_id") - ) + external_id=self.kwargs.get("patient_external_id"), + ), ) if not patient.is_active: raise ValidationError( - {"patient": "Only active patients data can be updated"} + {"patient": "Only active patients data can be updated"}, ) return serializer.save( facility=patient.facility, diff --git a/care/facility/api/viewsets/patient_consultation.py b/care/facility/api/viewsets/patient_consultation.py index d147ea611d..e365dc37eb 100644 --- a/care/facility/api/viewsets/patient_consultation.py +++ b/care/facility/api/viewsets/patient_consultation.py @@ -59,12 +59,11 @@ class PatientConsultationViewSet( def get_serializer_class(self): if self.action == "patient_from_asset": return PatientConsultationIDSerializer - elif self.action == "discharge_patient": + if self.action == "discharge_patient": return PatientConsultationDischargeSerializer - elif self.action == "email_discharge_summary": + if self.action == "email_discharge_summary": return EmailDischargeSummarySerializer - else: - return self.serializer_class + return self.serializer_class def get_permissions(self): if self.action == "patient_from_asset": @@ -86,13 +85,13 @@ def get_queryset(self): ) if self.request.user.is_superuser: return self.queryset - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: return self.queryset.filter( - patient__facility__state=self.request.user.state + patient__facility__state=self.request.user.state, ) - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: return self.queryset.filter( - patient__facility__district=self.request.user.district + patient__facility__district=self.request.user.district, ) allowed_facilities = get_accessible_facilities(self.request.user) applied_filters = Q(patient__facility__id__in=allowed_facilities) @@ -119,7 +118,7 @@ def _generate_discharge_summary(self, consultation_ext_id: str): "detail": ( "Discharge Summary is already being generated, " f"current progress {current_progress}%" - ) + ), }, status=status.HTTP_406_NOT_ACCEPTABLE, ) @@ -146,7 +145,7 @@ def generate_discharge_summary(self, request, *args, **kwargs): "detail": ( "Cannot generate a new discharge summary for already " "discharged patient" - ) + ), }, status=status.HTTP_406_NOT_ACCEPTABLE, ) @@ -189,13 +188,14 @@ def email_discharge_summary(self, request, *args, **kwargs): "detail": ( "Discharge Summary is already being generated, " f"current progress {existing_progress}%" - ) + ), }, status=status.HTTP_406_NOT_ACCEPTABLE, ) serializer = self.get_serializer( - data=request.data, context={"request": request} + data=request.data, + context={"request": request}, ) serializer.is_valid(raise_exception=True) email = serializer.validated_data["email"] @@ -221,7 +221,8 @@ def email_discharge_summary(self, request, *args, **kwargs): ) @extend_schema( - responses={200: PatientConsultationIDSerializer}, tags=["consultation", "asset"] + responses={200: PatientConsultationIDSerializer}, + tags=["consultation", "asset"], ) @action(detail=False, methods=["GET"]) def patient_from_asset(self, request): diff --git a/care/facility/api/viewsets/patient_external_test.py b/care/facility/api/viewsets/patient_external_test.py index 422d0bb512..c0a51ba2dc 100644 --- a/care/facility/api/viewsets/patient_external_test.py +++ b/care/facility/api/viewsets/patient_external_test.py @@ -30,7 +30,7 @@ def prettyerrors(errors): pretty_errors = defaultdict(list) - for attribute in PatientExternalTest.HEADER_CSV_MAPPING.keys(): + for attribute in PatientExternalTest.HEADER_CSV_MAPPING: if attribute in errors: for error in errors.get(attribute, ""): pretty_errors[attribute].append(str(error)) @@ -46,15 +46,15 @@ def filter(self, qs, value): self.field_name + "__in": values, self.field_name + "__isnull": False, } - qs = qs.filter(**_filter) - return qs + return qs.filter(**_filter) class PatientExternalTestFilter(filters.FilterSet): name = filters.CharFilter(field_name="name", lookup_expr="icontains") srf_id = filters.CharFilter(field_name="srf_id", lookup_expr="icontains") mobile_number = filters.CharFilter( - field_name="mobile_number", lookup_expr="icontains" + field_name="mobile_number", + lookup_expr="icontains", ) wards = MFilter(field_name="ward__id") districts = MFilter(field_name="district__id") @@ -93,14 +93,15 @@ def get_queryset(self): queryset = queryset.filter(local_body=self.request.user.local_body) elif self.request.user.user_type >= User.TYPE_VALUE_MAP["WardAdmin"]: queryset = queryset.filter( - ward=self.request.user.ward, ward__isnull=False + ward=self.request.user.ward, + ward__isnull=False, ) else: queryset = queryset.none() return queryset def get_serializer_class(self): - if self.action == "update" or self.action == "partial_update": + if self.action in {"update", "partial_update"}: return PatientExternalTestUpdateSerializer return super().get_serializer_class() @@ -127,7 +128,7 @@ def list(self, request, *args, **kwargs): field_header_map=mapping, field_serializer_map=pretty_mapping, ) - return super(PatientExternalTestViewSet, self).list(request, *args, **kwargs) + return super().list(request, *args, **kwargs) @extend_schema(tags=["external_result"]) @action(methods=["POST"], detail=False) diff --git a/care/facility/api/viewsets/patient_investigation.py b/care/facility/api/viewsets/patient_investigation.py index f575a33fe2..1295d7a035 100644 --- a/care/facility/api/viewsets/patient_investigation.py +++ b/care/facility/api/viewsets/patient_investigation.py @@ -45,8 +45,7 @@ def filter(self, qs, value): if not value: return qs - qs = qs.filter(groups__external_id=value) - return qs + return qs.filter(groups__external_id=value) class PatientInvestigationFilter(filters.FilterSet): @@ -55,7 +54,9 @@ class PatientInvestigationFilter(filters.FilterSet): class InvestigationGroupViewset( - mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + viewsets.GenericViewSet, ): serializer_class = PatientInvestigationGroupSerializer queryset = PatientInvestigationGroup.objects.all() @@ -71,7 +72,9 @@ class InvestigationResultsSetPagination(PageNumberPagination): class PatientInvestigationViewSet( - mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + viewsets.GenericViewSet, ): serializer_class = PatientInvestigationSerializer queryset = PatientInvestigation.objects.all() @@ -96,7 +99,9 @@ class InvestigationSummaryResultsSetPagination(PageNumberPagination): class PatientInvestigationSummaryViewSet( - mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + viewsets.GenericViewSet, ): serializer_class = InvestigationValueSerializer queryset = InvestigationValue.objects.all() @@ -113,7 +118,7 @@ def get_queryset(self): session_page = int(session_page) investigations = self.request.GET.get("investigations", "") queryset = self.queryset.filter( - consultation__patient__external_id=self.kwargs.get("patient_external_id") + consultation__patient__external_id=self.kwargs.get("patient_external_id"), ) sessions = ( queryset.filter(investigation__external_id__in=investigations.split(",")) @@ -129,13 +134,13 @@ def get_queryset(self): queryset = queryset.filter(session_id__in=sessions.values("session_id")) if self.request.user.is_superuser: return queryset - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: return queryset.filter( - consultation__patient__facility__state=self.request.user.state + consultation__patient__facility__state=self.request.user.state, ) - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: return queryset.filter( - consultation__patient__facility__district=self.request.user.district + consultation__patient__facility__district=self.request.user.district, ) allowed_facilities = get_accessible_facilities(self.request.user) filters = Q(consultation__patient__facility_id__in=allowed_facilities) @@ -169,20 +174,20 @@ def get_serializer_class(self): def get_queryset(self): queryset = self.queryset.filter( - consultation__external_id=self.kwargs.get("consultation_external_id") + consultation__external_id=self.kwargs.get("consultation_external_id"), ) if self.request.user.is_superuser: return queryset - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: return queryset.filter( - consultation__patient__facility__state=self.request.user.state + consultation__patient__facility__state=self.request.user.state, ) - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: return queryset.filter( - consultation__patient__facility__district=self.request.user.district + consultation__patient__facility__district=self.request.user.district, ) filters = Q( - consultation__patient__facility__users__id__exact=self.request.user.id + consultation__patient__facility__users__id__exact=self.request.user.id, ) filters |= Q(consultation__assigned_to=self.request.user) filters |= Q(consultation__patient__assigned_to=self.request.user) @@ -202,17 +207,18 @@ def get_sessions(self, request, *args, **kwargs): session_external_id=F("session__external_id"), session_created_date=F("session__created_date"), ) - .values("session_external_id", "session_created_date") - ) + .values("session_external_id", "session_created_date"), + ), ) + # TODO: change url to batch_update @extend_schema( request=InvestigationUpdateSerializer, responses={204: "Operation successful"}, tags=["investigation"], ) - @action(detail=False, methods=["PUT"]) - def batchUpdate(self, request, *args, **kwargs): + @action(detail=False, methods=["PUT"], url_path="batchUpdate") + def batch_update(self, request, *args, **kwargs): if "investigations" not in request.data: return Response( {"investigation": "is required"}, @@ -229,7 +235,7 @@ def batchUpdate(self, request, *args, **kwargs): ) consultation = PatientConsultation.objects.get( - external_id=kwargs.get("consultation_external_id") + external_id=kwargs.get("consultation_external_id"), ) queryset = queryset.filter(consultation=consultation) @@ -242,7 +248,8 @@ def batchUpdate(self, request, *args, **kwargs): if not obj: raise ValidationError({investigation["external_id"]: "not found"}) serializer_obj = InvestigationValueSerializer( - instance=obj, data=investigation + instance=obj, + data=investigation, ) serializer_obj.is_valid(raise_exception=True) serializer_obj.update(obj, investigation) @@ -265,7 +272,7 @@ def create(self, request, *args, **kwargs): ) consultation = PatientConsultation.objects.get( - external_id=kwargs.get("consultation_external_id") + external_id=kwargs.get("consultation_external_id"), ) consultation_id = consultation.id diff --git a/care/facility/api/viewsets/patient_otp.py b/care/facility/api/viewsets/patient_otp.py index af365533ce..553bf5be93 100644 --- a/care/facility/api/viewsets/patient_otp.py +++ b/care/facility/api/viewsets/patient_otp.py @@ -34,12 +34,16 @@ def login(self, request): try: mobile_validator(phone_number) except error: - raise ValidationError({"phone_number": "Invalid phone number format"}) + raise ValidationError( + {"phone_number": "Invalid phone number format"}, + ) from None if len(otp) != settings.OTP_LENGTH: raise ValidationError({"otp": "Invalid OTP"}) otp_object = PatientMobileOTP.objects.filter( - phone_number=phone_number, otp=otp, is_used=False + phone_number=phone_number, + otp=otp, + is_used=False, ).first() if not otp_object: diff --git a/care/facility/api/viewsets/patient_otp_data.py b/care/facility/api/viewsets/patient_otp_data.py index a3c0302788..5230c50007 100644 --- a/care/facility/api/viewsets/patient_otp_data.py +++ b/care/facility/api/viewsets/patient_otp_data.py @@ -30,5 +30,4 @@ def get_queryset(self): def get_serializer_class(self): if self.action == "list": return PatientListSerializer - else: - return self.serializer_class + return self.serializer_class diff --git a/care/facility/api/viewsets/patient_sample.py b/care/facility/api/viewsets/patient_sample.py index 419e7a0126..a449d2ed8b 100644 --- a/care/facility/api/viewsets/patient_sample.py +++ b/care/facility/api/viewsets/patient_sample.py @@ -44,12 +44,14 @@ def filter_queryset(self, request, queryset, view): class PatientSampleFilterSet(filters.FilterSet): district = filters.NumberFilter(field_name="consultation__facility__district_id") district_name = filters.CharFilter( - field_name="consultation__facility__district__name", lookup_expr="icontains" + field_name="consultation__facility__district__name", + lookup_expr="icontains", ) status = filters.ChoiceFilter(choices=PatientSample.SAMPLE_TEST_FLOW_CHOICES) result = filters.ChoiceFilter(choices=PatientSample.SAMPLE_TEST_RESULT_CHOICES) patient_name = filters.CharFilter( - field_name="patient__name", lookup_expr="icontains" + field_name="patient__name", + lookup_expr="icontains", ) facility = filters.UUIDFilter(field_name="consultation__facility__external_id") sample_type = filters.ChoiceFilter(choices=SAMPLE_TYPE_CHOICES) @@ -96,10 +98,10 @@ def get_serializer_class(self): return serializer_class def get_queryset(self): - queryset = super(PatientSampleViewSet, self).get_queryset() + queryset = super().get_queryset() if self.kwargs.get("patient_external_id") is not None: queryset = queryset.filter( - patient__external_id=self.kwargs.get("patient_external_id") + patient__external_id=self.kwargs.get("patient_external_id"), ) return queryset @@ -119,30 +121,30 @@ def list(self, request, *args, **kwargs): """ if settings.CSV_REQUEST_PARAMETER in request.GET: queryset = self.filter_queryset(self.get_queryset()).values( - *PatientSample.CSV_MAPPING.keys() + *PatientSample.CSV_MAPPING.keys(), ) return render_to_csv_response( queryset, field_header_map=PatientSample.CSV_MAPPING, field_serializer_map=PatientSample.CSV_MAKE_PRETTY, ) - return super(PatientSampleViewSet, self).list(request, *args, **kwargs) + return super().list(request, *args, **kwargs) def perform_create(self, serializer): validated_data = serializer.validated_data if self.kwargs.get("patient_pk") is not None: validated_data["patient"] = PatientRegistration.objects.get( - id=self.kwargs.get("patient_pk") + id=self.kwargs.get("patient_pk"), ) if not validated_data.get("patient") and not validated_data.get("consultation"): raise ValidationError( - {"non_field_errors": ["Either of patient or consultation is required"]} + {"non_field_errors": ["Either of patient or consultation is required"]}, ) if "consultation" not in validated_data: validated_data["consultation"] = PatientConsultation.objects.filter( - patient=validated_data["patient"] + patient=validated_data["patient"], ).last() if not validated_data["consultation"]: raise ValidationError({"patient": ["Invalid id/ No consultation done"]}) @@ -151,18 +153,23 @@ def perform_create(self, serializer): notes = validated_data.pop("notes", "created") instance = serializer.create(validated_data) instance.patientsampleflow_set.create( - status=instance.status, notes=notes, created_by=self.request.user + status=instance.status, + notes=notes, + created_by=self.request.user, ) return instance def perform_update(self, serializer): validated_data = serializer.validated_data notes = validated_data.pop( - "notes", f"updated by {self.request.user.get_username()}" + "notes", + f"updated by {self.request.user.get_username()}", ) with transaction.atomic(): instance = serializer.update(serializer.instance, validated_data) instance.patientsampleflow_set.create( - status=instance.status, notes=notes, created_by=self.request.user + status=instance.status, + notes=notes, + created_by=self.request.user, ) return instance diff --git a/care/facility/api/viewsets/prescription.py b/care/facility/api/viewsets/prescription.py index c18d353bd5..988c4d683e 100644 --- a/care/facility/api/viewsets/prescription.py +++ b/care/facility/api/viewsets/prescription.py @@ -37,7 +37,9 @@ class MedicineAdminstrationFilter(filters.FilterSet): class MedicineAdministrationViewSet( - mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + GenericViewSet, ): serializer_class = MedicineAdministrationSerializer permission_classes = (IsAuthenticated,) @@ -49,8 +51,8 @@ class MedicineAdministrationViewSet( def get_consultation_obj(self): return get_object_or_404( get_consultation_queryset(self.request.user).filter( - external_id=self.kwargs["consultation_external_id"] - ) + external_id=self.kwargs["consultation_external_id"], + ), ) def get_queryset(self): @@ -79,8 +81,8 @@ class ConsultationPrescriptionViewSet( def get_consultation_obj(self): return get_object_or_404( get_consultation_queryset(self.request.user).filter( - external_id=self.kwargs["consultation_external_id"] - ) + external_id=self.kwargs["consultation_external_id"], + ), ) def get_queryset(self): @@ -100,7 +102,8 @@ def discontinue(self, request, *args, **kwargs): prescription_obj = self.get_object() prescription_obj.discontinued = True prescription_obj.discontinued_reason = request.data.get( - "discontinued_reason", None + "discontinued_reason", + None, ) prescription_obj.save() return Response({}, status=status.HTTP_200_OK) @@ -119,28 +122,13 @@ def administer(self, request, *args, **kwargs): status=status.HTTP_400_BAD_REQUEST, ) serializer = MedicineAdministrationSerializer( - data=request.data, context={"prescription": prescription_obj} + data=request.data, + context={"prescription": prescription_obj}, ) serializer.is_valid(raise_exception=True) serializer.save(prescription=prescription_obj, administered_by=request.user) return Response(serializer.data, status=status.HTTP_201_CREATED) - # @action(methods=["GET"], detail=True) - # def get_administrations(self, request, *args, **kwargs): - # prescription_obj = self.get_object() - # serializer = MedicineAdministrationSerializer( - # MedicineAdministration.objects.filter(prescription_id=prescription_obj.id), - # many=True) - # return Response(serializer.data) - - # @action(methods=["DELETE"], detail=True) - # def delete_administered(self, request, *args, **kwargs): - # if not request.query_params.get("id", None): - # return Response({"success": False, "error": "id is required"}, status=status.HTTP_400_BAD_REQUEST) - # administered_obj = MedicineAdministration.objects.get(external_id=request.query_params.get("id", None)) - # administered_obj.delete() - # return Response({"success": True}, status=status.HTTP_200_OK) - class MedibaseViewSet(ViewSet): permission_classes = (IsAuthenticated,) @@ -185,8 +173,8 @@ def list(self, request): queryset = MedibaseMedicineTable - if type := request.query_params.get("type"): - queryset = [x for x in queryset if x[2] == type] + if medicine_type := request.query_params.get("type"): + queryset = [x for x in queryset if x[2] == medicine_type] if query := request.query_params.get("query"): query = query.strip().lower() diff --git a/care/facility/api/viewsets/prescription_supplier.py b/care/facility/api/viewsets/prescription_supplier.py index 8ff837f455..26defc5301 100644 --- a/care/facility/api/viewsets/prescription_supplier.py +++ b/care/facility/api/viewsets/prescription_supplier.py @@ -17,13 +17,16 @@ class PrescriptionSupplierConsultationFilter(filters.FilterSet): patient = filters.CharFilter(field_name="patient__external_id") patient_name = filters.CharFilter( - field_name="patient__name", lookup_expr="icontains" + field_name="patient__name", + lookup_expr="icontains", ) facility = filters.UUIDFilter(field_name="facility_external_id") class PrescriptionSupplierConsultationViewSet( - mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + GenericViewSet, ): lookup_field = "external_id" serializer_class = PrescriptionSupplierConsultationSerializer @@ -39,24 +42,25 @@ class PrescriptionSupplierConsultationViewSet( def get_queryset(self): if self.request.user.is_superuser: return self.queryset - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: return self.queryset.filter( - patient__facility__district=self.request.user.district + patient__facility__district=self.request.user.district, ) - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: return self.queryset.filter( - patient__facility__state=self.request.user.state + patient__facility__state=self.request.user.state, ) return self.queryset.filter( Q(patient__created_by=self.request.user) - | Q(facility__users__id__exact=self.request.user.id) + | Q(facility__users__id__exact=self.request.user.id), ).distinct("id") class PrescriptionSupplierFilter(filters.FilterSet): patient = filters.CharFilter(field_name="consultation__patient__external_id") patient_name = filters.CharFilter( - field_name="consultation__patient__name", lookup_expr="icontains" + field_name="consultation__patient__name", + lookup_expr="icontains", ) facility = filters.UUIDFilter(field_name="facility_external_id") @@ -82,8 +86,8 @@ class PrescriptionSupplierViewSet( def get_queryset(self): if self.request.user.is_superuser: return self.queryset - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: return self.queryset.filter(facility__district=self.request.user.district) - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: return self.queryset.filter(facility__state=self.request.user.state) return self.queryset.filter(facility__users__id__exact=self.request.user.id) diff --git a/care/facility/api/viewsets/resources.py b/care/facility/api/viewsets/resources.py index cfddd2d2b8..15dd34cdb8 100644 --- a/care/facility/api/viewsets/resources.py +++ b/care/facility/api/viewsets/resources.py @@ -46,7 +46,7 @@ def get_request_queryset(request, queryset): q_objects |= Q(approving_facility__state=request.user.state) q_objects |= Q(assigned_facility__state=request.user.state) return queryset.filter(q_objects) - elif request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + if request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: q_objects = Q(origin_facility__district=request.user.district) q_objects |= Q(approving_facility__district=request.user.district) q_objects |= Q(assigned_facility__district=request.user.district) @@ -67,7 +67,7 @@ class ResourceFilterSet(filters.FilterSet): facility = filters.UUIDFilter(field_name="facility__external_id") origin_facility = filters.UUIDFilter(field_name="origin_facility__external_id") approving_facility = filters.UUIDFilter( - field_name="approving_facility__external_id" + field_name="approving_facility__external_id", ) assigned_facility = filters.UUIDFilter(field_name="assigned_facility__external_id") created_date = filters.DateFromToRangeFilter(field_name="created_date") @@ -129,7 +129,7 @@ def get_queryset(self): def list(self, request, *args, **kwargs): if settings.CSV_REQUEST_PARAMETER in request.GET: queryset = self.filter_queryset(self.get_queryset()).values( - *ResourceRequest.CSV_MAPPING.keys() + *ResourceRequest.CSV_MAPPING.keys(), ) return render_to_csv_response( queryset, @@ -153,36 +153,29 @@ class ResourceRequestCommentViewSet( def get_queryset(self): queryset = self.queryset.filter( - request__external_id=self.kwargs.get("resource_external_id") + request__external_id=self.kwargs.get("resource_external_id"), ) if self.request.user.is_superuser: pass + elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: + queryset = queryset.filter( + Q(request__origin_facility__state=self.request.user.state) + | Q(request__approving_facility__state=self.request.user.state) + | Q(request__assigned_facility__state=self.request.user.state), + ) + elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + queryset = queryset.filter( + Q(request__origin_facility__district=self.request.user.district) + | Q(request__approving_facility__district=self.request.user.district) + | Q(request__assigned_facility__district=self.request.user.district), + ) else: - if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: - q_objects = Q(request__origin_facility__state=self.request.user.state) - q_objects |= Q( - request__approving_facility__state=self.request.user.state - ) - q_objects |= Q( - request__assigned_facility__state=self.request.user.state - ) - return queryset.filter(q_objects) - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: - q_objects = Q( - request__origin_facility__district=self.request.user.district - ) - q_objects |= Q( - request__approving_facility__district=self.request.user.district - ) - q_objects |= Q( - request__assigned_facility__district=self.request.user.district - ) - return queryset.filter(q_objects) facility_ids = get_accessible_facilities(self.request.user) - q_objects = Q(request__origin_facility__id__in=facility_ids) - q_objects |= Q(request__approving_facility__id__in=facility_ids) - q_objects |= Q(request__assigned_facility__id__in=facility_ids) - queryset = queryset.filter(q_objects) + queryset = queryset.filter( + Q(request__origin_facility__id__in=facility_ids) + | Q(request__approving_facility__id__in=facility_ids) + | Q(request__assigned_facility__id__in=facility_ids), + ) return queryset def get_request(self): diff --git a/care/facility/api/viewsets/shifting.py b/care/facility/api/viewsets/shifting.py index e5e40d1b54..ebfec927b5 100644 --- a/care/facility/api/viewsets/shifting.py +++ b/care/facility/api/viewsets/shifting.py @@ -55,19 +55,22 @@ class ShiftingFilterSet(filters.FilterSet): breathlessness_level = CareChoiceFilter(choice_dict=inverse_breathlessness_level) disease_status = CareChoiceFilter( - choice_dict=DISEASE_STATUS_DICT, field_name="patient__disease_status" + choice_dict=DISEASE_STATUS_DICT, + field_name="patient__disease_status", ) facility = filters.UUIDFilter(field_name="facility__external_id") patient = filters.UUIDFilter(field_name="patient__external_id") patient_name = filters.CharFilter( - field_name="patient__name", lookup_expr="icontains" + field_name="patient__name", + lookup_expr="icontains", ) patient_phone_number = filters.CharFilter( - field_name="patient__phone_number", lookup_expr="icontains" + field_name="patient__phone_number", + lookup_expr="icontains", ) origin_facility = filters.UUIDFilter(field_name="origin_facility__external_id") shifting_approving_facility = filters.UUIDFilter( - field_name="shifting_approving_facility__external_id" + field_name="shifting_approving_facility__external_id", ) assigned_facility = filters.UUIDFilter(field_name="assigned_facility__external_id") emergency = filters.BooleanFilter(field_name="emergency") @@ -140,45 +143,55 @@ def get_serializer_class(self): @action(detail=True, methods=["POST"]) def transfer(self, request, *args, **kwargs): shifting_obj = self.get_object() - if has_facility_permission( - request.user, shifting_obj.shifting_approving_facility - ) or has_facility_permission(request.user, shifting_obj.assigned_facility): - if shifting_obj.assigned_facility and shifting_obj.status >= 70: - if shifting_obj.patient: - patient = shifting_obj.patient - patient.facility = shifting_obj.assigned_facility - patient.is_active = True - patient.allow_transfer = False - patient.save() - shifting_obj.status = 80 - shifting_obj.save(update_fields=["status"]) - # Discharge from all other active consultations - PatientConsultation.objects.filter( - patient=patient, discharge_date__isnull=True - ).update(discharge_date=localtime(now()), discharge_reason="REF") - ConsultationBed.objects.filter( - consultation=patient.last_consultation, - end_date__isnull=True, - ).update(end_date=localtime(now())) - - return Response( - {"transfer": "completed"}, status=status.HTTP_200_OK - ) + if ( + ( + has_facility_permission( + request.user, + shifting_obj.shifting_approving_facility, + ) + or has_facility_permission(request.user, shifting_obj.assigned_facility) + ) + and shifting_obj.assigned_facility + and shifting_obj.status >= 70 # noqa: PLR2004 + and shifting_obj.patient + ): + patient = shifting_obj.patient + patient.facility = shifting_obj.assigned_facility + patient.is_active = True + patient.allow_transfer = False + patient.save() + shifting_obj.status = 80 + shifting_obj.save(update_fields=["status"]) + # Discharge from all other active consultations + PatientConsultation.objects.filter( + patient=patient, + discharge_date__isnull=True, + ).update(discharge_date=localtime(now()), discharge_reason="REF") + ConsultationBed.objects.filter( + consultation=patient.last_consultation, + end_date__isnull=True, + ).update(end_date=localtime(now())) + + return Response( + {"transfer": "completed"}, + status=status.HTTP_200_OK, + ) return Response( - {"error": "Invalid Request"}, status=status.HTTP_400_BAD_REQUEST + {"error": "Invalid Request"}, + status=status.HTTP_400_BAD_REQUEST, ) def list(self, request, *args, **kwargs): if settings.CSV_REQUEST_PARAMETER in request.GET: queryset = self.filter_queryset(self.get_queryset()).values( - *ShiftingRequest.CSV_MAPPING.keys() + *ShiftingRequest.CSV_MAPPING.keys(), ) return render_to_csv_response( queryset, field_header_map=ShiftingRequest.CSV_MAPPING, field_serializer_map=ShiftingRequest.CSV_MAKE_PRETTY, ) - return super(ShiftingViewSet, self).list(request, *args, **kwargs) + return super().list(request, *args, **kwargs) class ShifitngRequestCommentViewSet( @@ -195,7 +208,7 @@ class ShifitngRequestCommentViewSet( def get_queryset(self): queryset = self.queryset.filter( - request__external_id=self.kwargs.get("shift_external_id") + request__external_id=self.kwargs.get("shift_external_id"), ) if self.request.user.is_superuser: pass @@ -203,21 +216,21 @@ def get_queryset(self): if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: q_objects = Q(request__origin_facility__state=self.request.user.state) q_objects |= Q( - request__shifting_approving_facility__state=self.request.user.state + request__shifting_approving_facility__state=self.request.user.state, ) q_objects |= Q( - request__assigned_facility__state=self.request.user.state + request__assigned_facility__state=self.request.user.state, ) return queryset.filter(q_objects) - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: q_objects = Q( - request__origin_facility__district=self.request.user.district + request__origin_facility__district=self.request.user.district, ) q_objects |= Q( - request__shifting_approving_facility__district=self.request.user.district + request__shifting_approving_facility__district=self.request.user.district, ) q_objects |= Q( - request__assigned_facility__district=self.request.user.district + request__assigned_facility__district=self.request.user.district, ) return queryset.filter(q_objects) facility_ids = get_accessible_facilities(self.request.user) diff --git a/care/facility/api/viewsets/summary.py b/care/facility/api/viewsets/summary.py index e223a204c4..37c3713859 100644 --- a/care/facility/api/viewsets/summary.py +++ b/care/facility/api/viewsets/summary.py @@ -63,7 +63,7 @@ def list(self, request, *args, **kwargs): class TriageSummaryViewSet(ListModelMixin, GenericViewSet): lookup_field = "external_id" queryset = FacilityRelatedSummary.objects.filter(s_type="TriageSummary").order_by( - "-created_date" + "-created_date", ) permission_classes = (IsAuthenticatedOrReadOnly,) serializer_class = FacilitySummarySerializer @@ -91,7 +91,7 @@ def list(self, request, *args, **kwargs): class TestsSummaryViewSet(ListModelMixin, GenericViewSet): lookup_field = "external_id" queryset = FacilityRelatedSummary.objects.filter(s_type="TestSummary").order_by( - "-created_date" + "-created_date", ) permission_classes = (IsAuthenticatedOrReadOnly,) serializer_class = FacilitySummarySerializer @@ -119,7 +119,7 @@ def list(self, request, *args, **kwargs): class PatientSummaryViewSet(ListModelMixin, GenericViewSet): lookup_field = "external_id" queryset = FacilityRelatedSummary.objects.filter(s_type="PatientSummary").order_by( - "-created_date" + "-created_date", ) permission_classes = (IsAuthenticatedOrReadOnly,) serializer_class = FacilitySummarySerializer diff --git a/care/facility/management/commands/add_daily_round_consultation.py b/care/facility/management/commands/add_daily_round_consultation.py index 680d01cd95..84eabcc199 100644 --- a/care/facility/management/commands/add_daily_round_consultation.py +++ b/care/facility/management/commands/add_daily_round_consultation.py @@ -8,26 +8,26 @@ class Command(BaseCommand): Management command to Populate daily round for consultations. """ - help = "Populate daily round for consultations" + help = "Populate daily round for consultations" # noqa: A003 def handle(self, *args, **options): consultations = list( PatientConsultation.objects.filter( - last_daily_round__isnull=True - ).values_list("external_id") + last_daily_round__isnull=True, + ).values_list("external_id"), ) total_count = len(consultations) - print(f"{total_count} Consultations need to be updated") + self.stdout.write(f"{total_count} Consultations need to be updated") i = 0 for consultation_eid in consultations: - if i > 10000 and i % 10000 == 0: - print(f"{i} operations performed") + if i > 10000 and i % 10000 == 0: # noqa: PLR2004 + self.stdout.write(f"{i} operations performed") i = i + 1 PatientConsultation.objects.filter(external_id=consultation_eid[0]).update( last_daily_round=DailyRound.objects.filter( - consultation__external_id=consultation_eid[0] + consultation__external_id=consultation_eid[0], ) .order_by("-created_date") - .first() + .first(), ) - print("Operation Completed") + self.stdout.write(self.style.SUCCESS("Successfully Synced")) diff --git a/care/facility/management/commands/clean_patient_phone_numbers.py b/care/facility/management/commands/clean_patient_phone_numbers.py index 059f0b1ec8..48f648e1b5 100644 --- a/care/facility/management/commands/clean_patient_phone_numbers.py +++ b/care/facility/management/commands/clean_patient_phone_numbers.py @@ -1,6 +1,3 @@ -import json -from typing import Optional - import phonenumbers from django.core.management.base import BaseCommand from phonenumber_field.serializerfields import PhoneNumberField @@ -22,9 +19,11 @@ class Command(BaseCommand): Management command to clean the phone number field of patient to support E164 format. """ - help = "Cleans the phone number field of patient to support E164 field" + help = ( # noqa: A003 + "Cleans the phone number field of patient to support E164 field" + ) - def handle(self, *args, **options) -> Optional[str]: + def handle(self, *args, **options) -> str | None: qs = PatientRegistration.objects.all() failed = [] for patient in qs: @@ -32,7 +31,18 @@ def handle(self, *args, **options) -> Optional[str]: patient.phone_number = to_phone_number_field(patient.phone_number) patient.save() except Exception: - failed.append({"id": patient.id, "phone_number": patient.phone_number}) - - print(f"Completed for {qs.count()} | Failed for {len(failed)}") - print(f"Failed for {json.dumps(failed)}") + self.stdout.write( + self.style.ERROR( + f"Failed to clean phone number for {patient.id} {patient.phone_number}", + ), + ) + + if failed: + self.stdout.write( + self.style.ERROR(f"Failed to sync {len(failed)} patients"), + ) + self.stdout.write( + self.style.SUCCESS( + f"Successfully Synced {qs.count() - len(failed)} patients", + ), + ) diff --git a/care/facility/management/commands/generate_jwks.py b/care/facility/management/commands/generate_jwks.py index 750f82d46a..48893f362f 100644 --- a/care/facility/management/commands/generate_jwks.py +++ b/care/facility/management/commands/generate_jwks.py @@ -8,7 +8,7 @@ class Command(BaseCommand): Generate JWKS """ - help = "Generate JWKS" + help = "Generate JWKS" # noqa: A003 def handle(self, *args, **options): - print(generate_encoded_jwks()) + self.stdout.write(generate_encoded_jwks()) diff --git a/care/facility/management/commands/load_dummy_data.py b/care/facility/management/commands/load_dummy_data.py index 1ce9e1d0d6..3a9f907246 100644 --- a/care/facility/management/commands/load_dummy_data.py +++ b/care/facility/management/commands/load_dummy_data.py @@ -11,14 +11,14 @@ class Command(BaseCommand): Usage: python manage.py load_dummy_data """ - help = "Loads dummy data that is intended to be used for development and testing purpose." + help = "Loads dummy data that is intended to be used for development and testing purpose." # noqa: A003 BASE_URL = "data/dummy/" def handle(self, *args, **options): env = os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") if "production" in env or "staging" in env: raise CommandError( - "This command is not intended to be run in production environment." + "This command is not intended to be run in production environment.", ) try: @@ -30,4 +30,4 @@ def handle(self, *args, **options): management.call_command("loaddata", self.BASE_URL + "cypress_users.json") management.call_command("loaddata", self.BASE_URL + "facility_users.json") except Exception as e: - raise CommandError(e) + raise CommandError("Failed to load data to the db") from e diff --git a/care/facility/management/commands/load_medicines_data.py b/care/facility/management/commands/load_medicines_data.py index 6a8666070f..42e5e5937a 100644 --- a/care/facility/management/commands/load_medicines_data.py +++ b/care/facility/management/commands/load_medicines_data.py @@ -11,14 +11,14 @@ class Command(BaseCommand): Usage: python manage.py load_medicines_data """ - help = "Loads Medibase Medicines into the database from medibase.json" + help = "Loads Medibase Medicines into the database from medibase.json" # noqa: A003 def fetch_data(self): - with open("data/medibase.json", "r") as json_file: + with open("data/medibase.json") as json_file: return json.load(json_file) def handle(self, *args, **options): - print("Loading Medibase Medicines into the database from medibase.json") + self.stdout.write("Loading Medibase Medicines into the database") medibase_objects = self.fetch_data() MedibaseMedicine.objects.bulk_create( @@ -37,3 +37,5 @@ def handle(self, *args, **options): batch_size=1000, ignore_conflicts=True, ) + + self.stdout.write(self.style.SUCCESS("Successfully Loaded")) diff --git a/care/facility/management/commands/load_meta_icd11_diagnosis.py b/care/facility/management/commands/load_meta_icd11_diagnosis.py index 1ffae591b0..58553516ea 100644 --- a/care/facility/management/commands/load_meta_icd11_diagnosis.py +++ b/care/facility/management/commands/load_meta_icd11_diagnosis.py @@ -11,7 +11,7 @@ class Command(BaseCommand): Usage: python manage.py load_meta_icd11_diagnosis """ - help = "Loads ICD11 data to a table in to database." + help = "Loads ICD11 data to a table in to database." # noqa: A003 data = [] roots_lookup = {} @@ -88,24 +88,24 @@ def my(x): # The following code is never executed as the `icd11.json` file is # pre-sorted and hence the parent is always present before the child. - print("Full-scan for", id, item["label"]) + self.stdout.write(f"Full-scan for {id} {item['label']}") return self.find_roots( [ icd11_object for icd11_object in self.data if icd11_object["ID"] == item["parentId"] - ][0] + ][0], ) def handle(self, *args, **options): - print("Loading ICD11 data to DB Table (meta_icd11_diagnosis)...") + self.stdout.write("Loading ICD11 data to DB Table (meta_icd11_diagnosis)...") try: self.data = fetch_data() def roots(item): roots = self.find_roots(item) mapped = self.ICD11_GROUP_LABEL_PRETTY.get( - roots["chapter"], roots["chapter"] + roots["chapter"], roots["chapter"], ) result = { self.CLASS_KIND_DB_KEYS.get(k, k): v for k, v in roots.items() @@ -131,8 +131,10 @@ def roots(item): ) for icd11_object in self.data if icd11_object["ID"].split("/")[-1].isnumeric() - ] + ], + ) + self.stdout.write( + self.style.SUCCESS("Successfully loaded ICD11 data to database"), ) - print("Done loading ICD11 data to database.") except Exception as e: - raise CommandError(e) + raise CommandError("Failed to load ICD11 data to database") from e diff --git a/care/facility/management/commands/port_patient_wards.py b/care/facility/management/commands/port_patient_wards.py index bde986cbfb..b8dab40bd8 100644 --- a/care/facility/management/commands/port_patient_wards.py +++ b/care/facility/management/commands/port_patient_wards.py @@ -9,7 +9,7 @@ class Command(BaseCommand): Management command to sync Date of Birth and Year of Birth and Age. """ - help = "Syncs the age of Patients based on Date of Birth and Year of Birth" + help = "Syncs the age of Patients based on Date of Birth and Year of Birth" # noqa: A003 def handle(self, *args, **options): qs = PatientRegistration.objects.all() @@ -22,7 +22,8 @@ def handle(self, *args, **options): continue local_body = patient.local_body ward_object = Ward.objects.filter( - local_body=local_body, number=old_ward + local_body=local_body, + number=old_ward, ).first() if ward_object is None: failed += 1 @@ -32,9 +33,6 @@ def handle(self, *args, **options): patient.save() except Exception: failed += 1 - print( - str(failed), - " failed operations ", - str(success), - " sucessfull operations", - ) + if failed: + self.stdout.write(self.style.ERROR(f"Failed to sync {failed} patients")) + self.stdout.write(self.style.SUCCESS(f"Successfully Synced {success} patients")) diff --git a/care/facility/management/commands/scrape_icd_data.py b/care/facility/management/commands/scrape_icd_data.py index 888be1e812..944a135555 100644 --- a/care/facility/management/commands/scrape_icd_data.py +++ b/care/facility/management/commands/scrape_icd_data.py @@ -1,6 +1,6 @@ from django.core.management.base import BaseCommand -from care.facility.tasks.icd.scraper import ICDScraper +from care.facility.utils.icd.scraper import ICDScraper class Command(BaseCommand): @@ -8,7 +8,7 @@ class Command(BaseCommand): Management command to scrape ICD11 Disease Data """ - help = "Dump ICD11 Data to Json" + help = "Dump ICD11 Data to Json" # noqa: A003 def handle(self, *args, **options): scraper = ICDScraper() diff --git a/care/facility/management/commands/summarize.py b/care/facility/management/commands/summarize.py index bc77fd5d03..bec57f7fbd 100644 --- a/care/facility/management/commands/summarize.py +++ b/care/facility/management/commands/summarize.py @@ -14,12 +14,12 @@ class Command(BaseCommand): Management command to Force Create Summary objects. """ - help = "Force Create Summary Objects" + help = "Force Create Summary Objects" # noqa: A003 def handle(self, *args, **options): patient_summary() - print("Patients Summarised") + self.stdout.write(self.style.SUCCESS("Patients Summarized")) facility_capacity_summary() - print("Capacity Summarised") + self.stdout.write(self.style.SUCCESS("Capacity Summarized")) district_patient_summary() - print("District Wise Patient Summarised") + self.stdout.write(self.style.SUCCESS("District Wise Patient Summarized")) diff --git a/care/facility/management/commands/sync_external_test_patient.py b/care/facility/management/commands/sync_external_test_patient.py index 08be07911d..fd09695932 100644 --- a/care/facility/management/commands/sync_external_test_patient.py +++ b/care/facility/management/commands/sync_external_test_patient.py @@ -9,13 +9,13 @@ class Command(BaseCommand): Management command to Sync the patient created flag in external tests. """ - help = "Sync the patient created flag in external tests" + help = "Sync the patient created flag in external tests" # noqa: A003 def handle(self, *args, **options): - print("Starting Sync") + self.stdout.write("Syncing Patient Created Flag") for patient in PatientRegistration.objects.all(): if patient.srf_id: PatientExternalTest.objects.filter( - srf_id__iexact=patient.srf_id + srf_id__iexact=patient.srf_id, ).update(patient_created=True) - print("Completed Sync") + self.stdout.write(self.style.SUCCESS("Successfully Synced")) diff --git a/care/facility/management/commands/sync_patient_age.py b/care/facility/management/commands/sync_patient_age.py index 8b2ff40321..d29f26093c 100644 --- a/care/facility/management/commands/sync_patient_age.py +++ b/care/facility/management/commands/sync_patient_age.py @@ -8,7 +8,7 @@ class Command(BaseCommand): Management command to sync Date of Birth and Year of Birth and Age. """ - help = "Syncs the age of Patients based on Date of Birth and Year of Birth" + help = "Syncs the age of Patients based on Date of Birth and Year of Birth" # noqa: A003 def handle(self, *args, **options): qs = PatientRegistration.objects.all() @@ -19,6 +19,8 @@ def handle(self, *args, **options): except Exception: failed += 1 if failed: - print(f"Failed for {failed} Patient") + self.stdout.write( + self.style.ERROR(f"Failed to Sync Age for {failed} Patients"), + ) else: - print("Successfully Synced Age") + self.stdout.write("Successfully Synced Age") diff --git a/care/facility/models/ambulance.py b/care/facility/models/ambulance.py index e0b7a07751..e70aa6da6e 100644 --- a/care/facility/models/ambulance.py +++ b/care/facility/models/ambulance.py @@ -28,14 +28,11 @@ class Ambulance(FacilityBaseModel): owner_name = models.CharField(max_length=255) owner_phone_number = models.CharField( - max_length=14, validators=[mobile_or_landline_number_validator] + max_length=14, + validators=[mobile_or_landline_number_validator], ) owner_is_smart_phone = models.BooleanField(default=True) - # primary_district = models.IntegerField(choices=DISTRICT_CHOICES, blank=False) - # secondary_district = models.IntegerField(choices=DISTRICT_CHOICES, blank=True, null=True) - # third_district = models.IntegerField(choices=DISTRICT_CHOICES, blank=True, null=True) - primary_district = models.ForeignKey( District, on_delete=models.PROTECT, @@ -65,13 +62,18 @@ class Ambulance(FacilityBaseModel): insurance_valid_till_year = models.IntegerField(choices=INSURANCE_YEAR_CHOICES) ambulance_type = models.IntegerField( - choices=AMBULANCE_TYPES, blank=False, default=1 + choices=AMBULANCE_TYPES, + blank=False, + default=1, ) price_per_km = models.DecimalField(max_digits=7, decimal_places=2, null=True) has_free_service = models.BooleanField(default=False) created_by = models.ForeignKey( - User, on_delete=models.SET_NULL, null=True, blank=True + User, + on_delete=models.SET_NULL, + null=True, + blank=True, ) @property @@ -122,22 +124,14 @@ def has_object_update_permission(self, request): ) ) - # class Meta: - # constraints = [ - # models.CheckConstraint( - # name="ambulance_free_or_price", - # check=models.Q(price_per_km__isnull=False) - # | models.Q(has_free_service=True), - # ) - # ] - class AmbulanceDriver(FacilityBaseModel): ambulance = models.ForeignKey(Ambulance, on_delete=models.CASCADE) name = models.CharField(max_length=255) phone_number = models.CharField( - max_length=14, validators=[mobile_or_landline_number_validator] + max_length=14, + validators=[mobile_or_landline_number_validator], ) is_smart_phone = models.BooleanField() diff --git a/care/facility/models/asset.py b/care/facility/models/asset.py index 353a2d5a84..eed354ccc7 100644 --- a/care/facility/models/asset.py +++ b/care/facility/models/asset.py @@ -40,10 +40,14 @@ class RoomType(enum.Enum): name = models.CharField(max_length=1024, blank=False, null=False) description = models.TextField(default="", null=True, blank=True) location_type = models.IntegerField( - choices=RoomTypeChoices, default=RoomType.OTHER.value + choices=RoomTypeChoices, + default=RoomType.OTHER.value, ) facility = models.ForeignKey( - Facility, on_delete=models.PROTECT, null=False, blank=False + Facility, + on_delete=models.PROTECT, + null=False, + blank=False, ) @@ -72,21 +76,31 @@ class Asset(BaseModel): name = models.CharField(max_length=1024, blank=False, null=False) description = models.TextField(default="", null=True, blank=True) asset_type = models.IntegerField( - choices=AssetTypeChoices, default=AssetType.INTERNAL.value + choices=AssetTypeChoices, + default=AssetType.INTERNAL.value, ) asset_class = models.CharField( - choices=AssetClassChoices, default=None, null=True, blank=True, max_length=20 + choices=AssetClassChoices, + default=None, + null=True, + blank=True, + max_length=20, ) status = models.IntegerField(choices=StatusChoices, default=Status.ACTIVE.value) current_location = models.ForeignKey( - AssetLocation, on_delete=models.PROTECT, null=False, blank=False + AssetLocation, + on_delete=models.PROTECT, + null=False, + blank=False, ) is_working = models.BooleanField(default=None, null=True, blank=True) not_working_reason = models.CharField(max_length=1024, blank=True, null=True) serial_number = models.CharField(max_length=1024, blank=True, null=True) warranty_details = models.TextField(null=True, blank=True, default="") # Deprecated meta = JSONField( - default=dict, blank=True, validators=[JSONFieldSchemaValidator(ASSET_META)] + default=dict, + blank=True, + validators=[JSONFieldSchemaValidator(ASSET_META)], ) # Vendor Details vendor_name = models.CharField(max_length=1024, blank=True, null=True) @@ -187,16 +201,25 @@ def __str__(self): class UserDefaultAssetLocation(BaseModel): user = models.ForeignKey(User, on_delete=models.PROTECT, null=False, blank=False) location = models.ForeignKey( - AssetLocation, on_delete=models.PROTECT, null=False, blank=False + AssetLocation, + on_delete=models.PROTECT, + null=False, + blank=False, ) class FacilityDefaultAssetLocation(BaseModel): facility = models.ForeignKey( - Facility, on_delete=models.PROTECT, null=False, blank=False + Facility, + on_delete=models.PROTECT, + null=False, + blank=False, ) location = models.ForeignKey( - AssetLocation, on_delete=models.PROTECT, null=False, blank=False + AssetLocation, + on_delete=models.PROTECT, + null=False, + blank=False, ) @@ -217,7 +240,10 @@ class AssetTransaction(BaseModel): blank=False, ) performed_by = models.ForeignKey( - User, on_delete=models.PROTECT, null=False, blank=False + User, + on_delete=models.PROTECT, + null=False, + blank=False, ) @@ -242,7 +268,10 @@ class AssetServiceEdit(models.Model): ) edited_on = models.DateTimeField(auto_now_add=True) edited_by = models.ForeignKey( - User, on_delete=models.PROTECT, null=False, blank=False + User, + on_delete=models.PROTECT, + null=False, + blank=False, ) serviced_on = models.DateField() @@ -250,3 +279,6 @@ class AssetServiceEdit(models.Model): class Meta: ordering = ["-edited_on"] + + def __str__(self): + return f"{self.asset_service.asset.name} - {self.serviced_on}" diff --git a/care/facility/models/bed.py b/care/facility/models/bed.py index 205127711f..3299c31cf5 100644 --- a/care/facility/models/bed.py +++ b/care/facility/models/bed.py @@ -19,15 +19,22 @@ class Bed(BaseModel): name = models.CharField(max_length=1024) description = models.TextField(default="", blank=True) bed_type = models.IntegerField( - choices=BedTypeChoices, default=BedType.REGULAR.value + choices=BedTypeChoices, + default=BedType.REGULAR.value, ) facility = models.ForeignKey( - Facility, on_delete=models.PROTECT, null=False, blank=False + Facility, + on_delete=models.PROTECT, + null=False, + blank=False, ) # Deprecated meta = JSONField(default=dict, blank=True) assets = models.ManyToManyField(Asset, through="AssetBed") location = models.ForeignKey( - AssetLocation, on_delete=models.PROTECT, null=False, blank=False + AssetLocation, + on_delete=models.PROTECT, + null=False, + blank=False, ) @property @@ -44,7 +51,7 @@ def validate(self) -> None: .exists() ): raise ValidationError( - {"name": "Bed with same name already exists in location."} + {"name": "Bed with same name already exists in location."}, ) def save(self, *args, **kwargs) -> None: @@ -67,14 +74,19 @@ def __str__(self): class ConsultationBed(BaseModel): consultation = models.ForeignKey( - PatientConsultation, on_delete=models.PROTECT, null=False, blank=False + PatientConsultation, + on_delete=models.PROTECT, + null=False, + blank=False, ) bed = models.ForeignKey(Bed, on_delete=models.PROTECT, null=False, blank=False) start_date = models.DateTimeField(null=False, blank=False) end_date = models.DateTimeField(null=True, blank=True, default=None) meta = JSONField(default=dict, blank=True) assets = models.ManyToManyField( - Asset, through="ConsultationBedAsset", related_name="assigned_consultation_beds" + Asset, + through="ConsultationBedAsset", + related_name="assigned_consultation_beds", ) diff --git a/care/facility/models/daily_round.py b/care/facility/models/daily_round.py index 89575f5058..8187a4ade6 100644 --- a/care/facility/models/daily_round.py +++ b/care/facility/models/daily_round.py @@ -122,7 +122,9 @@ class InsulinIntakeFrequencyType(enum.Enum): ] consultation = models.ForeignKey( - PatientConsultation, on_delete=models.PROTECT, related_name="daily_rounds" + PatientConsultation, + on_delete=models.PROTECT, + related_name="daily_rounds", ) temperature = models.DecimalField( decimal_places=2, @@ -133,7 +135,11 @@ class InsulinIntakeFrequencyType(enum.Enum): validators=[MinValueValidator(95), MaxValueValidator(106)], ) spo2 = models.DecimalField( - max_digits=4, decimal_places=2, blank=True, null=True, default=None + max_digits=4, + decimal_places=2, + blank=True, + null=True, + default=None, ) temperature_measured_at = models.DateTimeField(null=True, blank=True) physical_examination_info = models.TextField(null=True, blank=True) @@ -153,13 +159,19 @@ class InsulinIntakeFrequencyType(enum.Enum): null=True, ) # Deprecated patient_category = models.CharField( - choices=CATEGORY_CHOICES, max_length=8, blank=False, null=True + choices=CATEGORY_CHOICES, + max_length=8, + blank=False, + null=True, ) current_health = models.IntegerField( - default=0, choices=CURRENT_HEALTH_CHOICES, blank=True + default=0, + choices=CURRENT_HEALTH_CHOICES, + blank=True, ) recommend_discharge = models.BooleanField( - default=False, verbose_name="Recommend Discharging Patient" + default=False, + verbose_name="Recommend Discharging Patient", ) other_details = models.TextField(null=True, blank=True) medication_given = JSONField(default=dict) # To be Used Later on @@ -168,7 +180,10 @@ class InsulinIntakeFrequencyType(enum.Enum): created_by_telemedicine = models.BooleanField(default=False) created_by = models.ForeignKey( - User, on_delete=models.SET_NULL, null=True, related_name="update_created_user" + User, + on_delete=models.SET_NULL, + null=True, + related_name="update_created_user", ) last_edited_by = models.ForeignKey( @@ -181,13 +196,15 @@ class InsulinIntakeFrequencyType(enum.Enum): taken_at = models.DateTimeField(null=True, blank=True, db_index=True) rounds_type = models.IntegerField( - choices=RoundsTypeChoice, default=RoundsType.NORMAL.value + choices=RoundsTypeChoice, + default=RoundsType.NORMAL.value, ) # Critical Care Attributes consciousness_level = models.IntegerField( - choices=ConsciousnessChoice, default=ConsciousnessType.UNKNOWN.value + choices=ConsciousnessChoice, + default=ConsciousnessType.UNKNOWN.value, ) consciousness_level_detail = models.TextField(default=None, null=True, blank=True) @@ -201,10 +218,13 @@ class InsulinIntakeFrequencyType(enum.Enum): ) left_pupil_size_detail = models.TextField(default=None, null=True, blank=True) left_pupil_light_reaction = models.IntegerField( - choices=PupilReactionChoice, default=PupilReactionType.UNKNOWN.value + choices=PupilReactionChoice, + default=PupilReactionType.UNKNOWN.value, ) left_pupil_light_reaction_detail = models.TextField( - default=None, null=True, blank=True + default=None, + null=True, + blank=True, ) right_pupil_size = models.IntegerField( default=None, @@ -214,10 +234,13 @@ class InsulinIntakeFrequencyType(enum.Enum): ) right_pupil_size_detail = models.TextField(default=None, null=True, blank=True) right_pupil_light_reaction = models.IntegerField( - choices=PupilReactionChoice, default=PupilReactionType.UNKNOWN.value + choices=PupilReactionChoice, + default=PupilReactionType.UNKNOWN.value, ) right_pupil_light_reaction_detail = models.TextField( - default=None, null=True, blank=True + default=None, + null=True, + blank=True, ) glasgow_eye_open = models.IntegerField( default=None, @@ -240,16 +263,20 @@ class InsulinIntakeFrequencyType(enum.Enum): validators=[MinValueValidator(3), MaxValueValidator(15)], ) limb_response_upper_extremity_right = models.IntegerField( - choices=LimbResponseChoice, default=LimbResponseType.UNKNOWN.value + choices=LimbResponseChoice, + default=LimbResponseType.UNKNOWN.value, ) limb_response_upper_extremity_left = models.IntegerField( - choices=LimbResponseChoice, default=LimbResponseType.UNKNOWN.value + choices=LimbResponseChoice, + default=LimbResponseType.UNKNOWN.value, ) limb_response_lower_extremity_left = models.IntegerField( - choices=LimbResponseChoice, default=LimbResponseType.UNKNOWN.value + choices=LimbResponseChoice, + default=LimbResponseType.UNKNOWN.value, ) limb_response_lower_extremity_right = models.IntegerField( - choices=LimbResponseChoice, default=LimbResponseType.UNKNOWN.value + choices=LimbResponseChoice, + default=LimbResponseType.UNKNOWN.value, ) bp = JSONField(default=dict, validators=[JSONFieldSchemaValidator(BLOOD_PRESSURE)]) pulse = models.IntegerField( @@ -265,10 +292,12 @@ class InsulinIntakeFrequencyType(enum.Enum): rhythm = models.IntegerField(choices=RythmnChoice, default=RythmnType.UNKNOWN.value) rhythm_detail = models.TextField(default=None, null=True, blank=True) ventilator_interface = models.IntegerField( - choices=VentilatorInterfaceChoice, default=VentilatorInterfaceType.UNKNOWN.value + choices=VentilatorInterfaceChoice, + default=VentilatorInterfaceType.UNKNOWN.value, ) ventilator_mode = models.IntegerField( - choices=VentilatorModeChoice, default=VentilatorModeType.UNKNOWN.value + choices=VentilatorModeChoice, + default=VentilatorModeType.UNKNOWN.value, ) ventilator_peep = models.DecimalField( decimal_places=2, @@ -339,7 +368,8 @@ class InsulinIntakeFrequencyType(enum.Enum): validators=[MinValueValidator(0), MaxValueValidator(10)], ) pain_scale_enhanced = JSONField( - default=list, validators=[JSONFieldSchemaValidator(PAIN_SCALE_ENHANCED)] + default=list, + validators=[JSONFieldSchemaValidator(PAIN_SCALE_ENHANCED)], ) ph = models.DecimalField( decimal_places=2, @@ -414,16 +444,25 @@ class InsulinIntakeFrequencyType(enum.Enum): default=InsulinIntakeFrequencyType.UNKNOWN.value, ) infusions = JSONField( - default=list, validators=[JSONFieldSchemaValidator(INFUSIONS)] + default=list, + validators=[JSONFieldSchemaValidator(INFUSIONS)], ) iv_fluids = JSONField(default=list, validators=[JSONFieldSchemaValidator(IV_FLUID)]) feeds = JSONField(default=list, validators=[JSONFieldSchemaValidator(FEED)]) total_intake_calculated = models.DecimalField( - decimal_places=2, max_digits=6, blank=True, default=None, null=True + decimal_places=2, + max_digits=6, + blank=True, + default=None, + null=True, ) output = JSONField(default=list, validators=[JSONFieldSchemaValidator(OUTPUT)]) total_output_calculated = models.DecimalField( - decimal_places=2, max_digits=6, blank=True, default=None, null=True + decimal_places=2, + max_digits=6, + blank=True, + default=None, + null=True, ) dialysis_fluid_balance = models.IntegerField( default=None, @@ -436,10 +475,12 @@ class InsulinIntakeFrequencyType(enum.Enum): validators=[MinValueValidator(0), MaxValueValidator(5000)], ) pressure_sore = JSONField( - default=list, validators=[JSONFieldSchemaValidator(PRESSURE_SORE)] + default=list, + validators=[JSONFieldSchemaValidator(PRESSURE_SORE)], ) nursing = JSONField( - default=list, validators=[JSONFieldSchemaValidator(NURSING_PROCEDURE)] + default=list, + validators=[JSONFieldSchemaValidator(NURSING_PROCEDURE)], ) medicine_administration = JSONField( @@ -494,17 +535,19 @@ def save(self, *args, **kwargs): # self.pressure_sore = self.update_pressure_sore() - super(DailyRound, self).save(*args, **kwargs) + super().save(*args, **kwargs) @staticmethod def has_write_permission(request): - if "/analyse" not in request.get_full_path(): - if ( - request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"] - ): - return False + if "/analyse" not in request.get_full_path() and ( + request.user.user_type + in { + User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StateReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StaffReadOnly"], + } + ): + return False return DailyRound.has_read_permission(request) @staticmethod @@ -516,8 +559,8 @@ def has_read_permission(request): return request.user.is_superuser or ( (request.user in consultation.patient.facility.users.all()) or ( - request.user == consultation.assigned_to - or request.user == consultation.patient.assigned_to + request.user + in (consultation.assigned_to, consultation.patient.assigned_to) ) or ( request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] @@ -537,8 +580,11 @@ def has_object_read_permission(self, request): and request.user in self.consultation.patient.facility.users.all() ) or ( - self.consultation.assigned_to == request.user - or request.user == self.consultation.patient.assigned_to + request.user + in { + self.consultation.assigned_to, + self.consultation.patient.assigned_to, + } ) or ( request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] @@ -559,11 +605,11 @@ def has_object_read_permission(self, request): ) def has_object_write_permission(self, request): - if ( - request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"] - ): + if request.user.user_type in { + User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StateReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StaffReadOnly"], + }: return False return ( request.user.is_superuser @@ -572,8 +618,11 @@ def has_object_write_permission(self, request): and self.consultation.patient.facility == request.user.home_facility ) or ( - self.consultation.assigned_to == request.user - or request.user == self.consultation.patient.assigned_to + request.user + in { + self.consultation.assigned_to, + self.consultation.patient.assigned_to, + } ) or ( request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] @@ -598,8 +647,9 @@ def has_object_asset_read_permission(self, request): def has_object_asset_write_permission(self, request): consultation = PatientConsultation.objects.select_related( - "current_bed__bed" + "current_bed__bed", ).get(external_id=request.parser_context["kwargs"]["consultation_external_id"]) return AssetBed.objects.filter( - asset=request.user.asset, bed=consultation.current_bed.bed + asset=request.user.asset, + bed=consultation.current_bed.bed, ).exists() diff --git a/care/facility/models/facility.py b/care/facility/models/facility.py index 16f4f0f39f..69d84a0e71 100644 --- a/care/facility/models/facility.py +++ b/care/facility/models/facility.py @@ -124,19 +124,31 @@ class Facility(FacilityBaseModel, FacilityPermissionMixin): ) longitude = models.DecimalField( - max_digits=22, decimal_places=16, null=True, blank=True + max_digits=22, + decimal_places=16, + null=True, + blank=True, ) latitude = models.DecimalField( - max_digits=22, decimal_places=16, null=True, blank=True + max_digits=22, + decimal_places=16, + null=True, + blank=True, ) pincode = models.IntegerField(default=None, null=True) address = models.TextField() ward = models.ForeignKey(Ward, on_delete=models.SET_NULL, null=True, blank=True) local_body = models.ForeignKey( - LocalBody, on_delete=models.SET_NULL, null=True, blank=True + LocalBody, + on_delete=models.SET_NULL, + null=True, + blank=True, ) district = models.ForeignKey( - District, on_delete=models.SET_NULL, null=True, blank=True + District, + on_delete=models.SET_NULL, + null=True, + blank=True, ) state = models.ForeignKey(State, on_delete=models.SET_NULL, null=True, blank=True) @@ -151,11 +163,16 @@ class Facility(FacilityBaseModel, FacilityPermissionMixin): expected_type_d_cylinders = models.IntegerField(default=0) phone_number = models.CharField( - max_length=14, blank=True, validators=[mobile_or_landline_number_validator] + max_length=14, + blank=True, + validators=[mobile_or_landline_number_validator], ) corona_testing = models.BooleanField(default=False) created_by = models.ForeignKey( - User, on_delete=models.SET_NULL, null=True, blank=True + User, + on_delete=models.SET_NULL, + null=True, + blank=True, ) users = models.ManyToManyField( @@ -166,7 +183,10 @@ class Facility(FacilityBaseModel, FacilityPermissionMixin): ) cover_image_url = models.CharField( - blank=True, null=True, default=None, max_length=500 + blank=True, + null=True, + default=None, + max_length=500, ) middleware_address = models.CharField(null=True, default=None, max_length=200) @@ -196,7 +216,9 @@ def save(self, *args, **kwargs) -> None: if is_create: FacilityUser.objects.create( - facility=self, user=self.created_by, created_by=self.created_by + facility=self, + user=self.created_by, + created_by=self.created_by, ) CSV_MAPPING = { @@ -229,13 +251,23 @@ class FacilityLocalGovtBody(models.Model): """ facility = models.OneToOneField( - Facility, unique=True, null=True, blank=True, on_delete=models.SET_NULL + Facility, + unique=True, + null=True, + blank=True, + on_delete=models.SET_NULL, ) local_body = models.ForeignKey( - LocalBody, null=True, blank=True, on_delete=models.SET_NULL + LocalBody, + null=True, + blank=True, + on_delete=models.SET_NULL, ) district = models.ForeignKey( - District, null=True, blank=True, on_delete=models.SET_NULL + District, + null=True, + blank=True, + on_delete=models.SET_NULL, ) class Meta: @@ -244,7 +276,7 @@ class Meta: name="cons_facilitylocalgovtbody_only_one_null", check=models.Q(local_body__isnull=False) | models.Q(district__isnull=False), - ) + ), ] def __str__(self): @@ -266,7 +298,10 @@ def save(self, *args, **kwargs) -> None: class HospitalDoctors(FacilityBaseModel, FacilityRelatedPermissionMixin): facility = models.ForeignKey( - "Facility", on_delete=models.CASCADE, null=False, blank=False + "Facility", + on_delete=models.CASCADE, + null=False, + blank=False, ) area = models.IntegerField(choices=DOCTOR_TYPES) count = models.IntegerField() @@ -280,7 +315,7 @@ class Meta: fields=["facility", "area"], condition=models.Q(deleted=False), name="unique_facility_doctor", - ) + ), ] CSV_RELATED_MAPPING = { @@ -293,7 +328,10 @@ class Meta: class FacilityCapacity(FacilityBaseModel, FacilityRelatedPermissionMixin): facility = models.ForeignKey( - "Facility", on_delete=models.CASCADE, null=False, blank=False + "Facility", + on_delete=models.CASCADE, + null=False, + blank=False, ) room_type = models.IntegerField(choices=ROOM_TYPES) total_capacity = models.IntegerField(default=0, validators=[MinValueValidator(0)]) @@ -307,7 +345,7 @@ class Meta: fields=["facility", "room_type"], condition=models.Q(deleted=False), name="unique_facility_room_type", - ) + ), ] verbose_name_plural = "Facility Capacities" @@ -332,7 +370,10 @@ def __str__(self): class FacilityStaff(FacilityBaseModel): facility = models.ForeignKey( - "Facility", on_delete=models.CASCADE, null=False, blank=False + "Facility", + on_delete=models.CASCADE, + null=False, + blank=False, ) staff = models.ForeignKey(User, on_delete=models.CASCADE, null=False, blank=False) @@ -342,10 +383,16 @@ def __str__(self): class FacilityVolunteer(FacilityBaseModel): facility = models.ForeignKey( - "Facility", on_delete=models.CASCADE, null=False, blank=False + "Facility", + on_delete=models.CASCADE, + null=False, + blank=False, ) volunteer = models.ForeignKey( - User, on_delete=models.CASCADE, null=False, blank=False + User, + on_delete=models.CASCADE, + null=False, + blank=False, ) def __str__(self): @@ -360,13 +407,17 @@ def __str__(self): class Building(FacilityBaseModel): facility = models.ForeignKey( - "Facility", on_delete=models.CASCADE, null=False, blank=False + "Facility", + on_delete=models.CASCADE, + null=False, + blank=False, ) name = models.CharField(max_length=1000) num_rooms = models.IntegerField(validators=[MinValueValidator(0)], default=0) num_floors = models.IntegerField(validators=[MinValueValidator(0)], default=0) num_buildings = models.IntegerField( - validators=[MinValueValidator(0)], default=0 + validators=[MinValueValidator(0)], + default=0, ) # For Internal Use only def __str__(self): @@ -381,7 +432,10 @@ def __str__(self): class Room(FacilityBaseModel): building = models.ForeignKey( - "Building", on_delete=models.CASCADE, null=False, blank=False + "Building", + on_delete=models.CASCADE, + null=False, + blank=False, ) num = models.CharField(max_length=1000) floor = models.IntegerField(validators=[MinValueValidator(0)], default=0) @@ -424,7 +478,10 @@ def __str__(self): class Inventory(FacilityBaseModel): facility = models.ForeignKey( - "Facility", on_delete=models.CASCADE, null=False, blank=False + "Facility", + on_delete=models.CASCADE, + null=False, + blank=False, ) item = models.ForeignKey("InventoryItem", on_delete=models.CASCADE) quantitiy = models.IntegerField(validators=[MinValueValidator(0)], default=0) @@ -447,7 +504,10 @@ class Meta: class InventoryLog(FacilityBaseModel): inventory = models.ForeignKey("Inventory", on_delete=models.CASCADE) updated_by = models.ForeignKey( - User, on_delete=models.SET_NULL, null=True, blank=True + User, + on_delete=models.SET_NULL, + null=True, + blank=True, ) prev_count = models.IntegerField(validators=[MinValueValidator(0)], default=0) new_count = models.IntegerField(validators=[MinValueValidator(0)], default=0) @@ -472,7 +532,9 @@ class FacilityUser(models.Model): facility = models.ForeignKey(Facility, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE) created_by = models.ForeignKey( - User, on_delete=models.PROTECT, related_name="created_users" + User, + on_delete=models.PROTECT, + related_name="created_users", ) class Meta: diff --git a/care/facility/models/file_upload.py b/care/facility/models/file_upload.py index 3fb68eac6a..e33d1fdc87 100644 --- a/care/facility/models/file_upload.py +++ b/care/facility/models/file_upload.py @@ -61,7 +61,8 @@ class FileCategory(enum.Enum): ) archived_datetime = models.DateTimeField(blank=True, null=True) file_type = models.IntegerField( - choices=FileTypeChoices, default=FileType.PATIENT.value + choices=FileTypeChoices, + default=FileType.PATIENT.value, ) file_category = models.CharField( choices=FileCategoryChoices, @@ -84,8 +85,8 @@ def save(self, *args, **kwargs): return super().save(*args, **kwargs) def signed_url(self, duration=60 * 60): - s3Client = boto3.client("s3", **cs_provider.get_client_config()) - return s3Client.generate_presigned_url( + s3 = boto3.client("s3", **cs_provider.get_client_config()) + return s3.generate_presigned_url( "put_object", Params={ "Bucket": settings.FILE_UPLOAD_BUCKET, @@ -95,8 +96,8 @@ def signed_url(self, duration=60 * 60): ) def read_signed_url(self, duration=60 * 60): - s3Client = boto3.client("s3", **cs_provider.get_client_config()) - return s3Client.generate_presigned_url( + s3 = boto3.client("s3", **cs_provider.get_client_config()) + return s3.generate_presigned_url( "get_object", Params={ "Bucket": settings.FILE_UPLOAD_BUCKET, diff --git a/care/facility/models/inventory.py b/care/facility/models/inventory.py index 934b0128f4..8a655b7d5e 100644 --- a/care/facility/models/inventory.py +++ b/care/facility/models/inventory.py @@ -82,7 +82,8 @@ class FacilityInventoryItem(models.Model): blank=False, ) allowed_units = models.ManyToManyField( - FacilityInventoryUnit, related_name="allowed_units" + FacilityInventoryUnit, + related_name="allowed_units", ) tags = models.ManyToManyField(FacilityInventoryItemTag) description = models.TextField(blank=True) @@ -101,20 +102,32 @@ class FacilityInventoryLog(FacilityBaseModel, FacilityRelatedPermissionMixin): """ facility = models.ForeignKey( - "Facility", on_delete=models.CASCADE, null=False, blank=False + "Facility", + on_delete=models.CASCADE, + null=False, + blank=False, ) item = models.ForeignKey( - FacilityInventoryItem, on_delete=models.SET_NULL, null=True, blank=False + FacilityInventoryItem, + on_delete=models.SET_NULL, + null=True, + blank=False, ) current_stock = models.FloatField(default=0) quantity_in_default_unit = models.FloatField(default=0) quantity = models.FloatField(default=0) unit = models.ForeignKey( - FacilityInventoryUnit, on_delete=models.SET_NULL, null=True, blank=False + FacilityInventoryUnit, + on_delete=models.SET_NULL, + null=True, + blank=False, ) is_incoming = models.BooleanField() created_by = models.ForeignKey( - User, on_delete=models.SET_NULL, null=True, blank=True + User, + on_delete=models.SET_NULL, + null=True, + blank=True, ) probable_accident = models.BooleanField(default=False) @@ -126,13 +139,19 @@ class FacilityInventorySummary(FacilityBaseModel, FacilityRelatedPermissionMixin """ facility = models.ForeignKey( - "Facility", on_delete=models.CASCADE, null=False, blank=False + "Facility", + on_delete=models.CASCADE, + null=False, + blank=False, ) item = models.ForeignKey( - FacilityInventoryItem, on_delete=models.SET_NULL, null=True, blank=False + FacilityInventoryItem, + on_delete=models.SET_NULL, + null=True, + blank=False, ) quantity = models.FloatField( - default=0 + default=0, ) # Automatically Set // NOT EDITABLE BY ADMIN is_low = models.BooleanField(default=False) @@ -142,7 +161,7 @@ class Meta: fields=["facility", "item"], condition=models.Q(deleted=False), name="unique_facility_item_summary", - ) + ), ] @@ -152,10 +171,16 @@ class FacilityInventoryMinQuantity(FacilityBaseModel, FacilityRelatedPermissionM """ facility = models.ForeignKey( - "Facility", on_delete=models.CASCADE, null=False, blank=False + "Facility", + on_delete=models.CASCADE, + null=False, + blank=False, ) item = models.ForeignKey( - FacilityInventoryItem, on_delete=models.SET_NULL, null=True, blank=False + FacilityInventoryItem, + on_delete=models.SET_NULL, + null=True, + blank=False, ) min_quantity = models.FloatField(default=0) @@ -165,7 +190,7 @@ class Meta: fields=["facility", "item"], condition=models.Q(deleted=False), name="unique_facility_item_min_quantity", - ) + ), ] @@ -175,10 +200,16 @@ class FacilityInventoryBurnRate(FacilityBaseModel, FacilityRelatedPermissionMixi """ facility = models.ForeignKey( - "Facility", on_delete=models.CASCADE, null=False, blank=False + "Facility", + on_delete=models.CASCADE, + null=False, + blank=False, ) item = models.ForeignKey( - FacilityInventoryItem, on_delete=models.SET_NULL, null=True, blank=False + FacilityInventoryItem, + on_delete=models.SET_NULL, + null=True, + blank=False, ) burn_rate = models.FloatField(default=0) current_stock = models.FloatField(default=0) @@ -193,6 +224,6 @@ class Meta: fields=( "facility", "item", - ) - ) + ), + ), ] diff --git a/care/facility/models/json_schema/__init__.py b/care/facility/models/json_schema/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/care/facility/models/json_schema/consultation.py b/care/facility/models/json_schema/consultation.py index 91e68402c2..a3558ee637 100644 --- a/care/facility/models/json_schema/consultation.py +++ b/care/facility/models/json_schema/consultation.py @@ -14,6 +14,6 @@ }, "additionalProperties": False, "required": ["start_date", "type", "site"], - } + }, ], } diff --git a/care/facility/models/json_schema/daily_round.py b/care/facility/models/json_schema/daily_round.py index 633e9e7bf5..2b0a3fd3dc 100644 --- a/care/facility/models/json_schema/daily_round.py +++ b/care/facility/models/json_schema/daily_round.py @@ -23,7 +23,7 @@ }, "additionalProperties": False, "required": ["name", "quantity"], - } + }, ], } @@ -39,7 +39,7 @@ }, "additionalProperties": False, "required": ["name", "quantity"], - } + }, ], } @@ -56,7 +56,7 @@ }, "additionalProperties": False, "required": ["name", "quantity"], - } + }, ], } @@ -72,7 +72,7 @@ }, "additionalProperties": False, "required": ["name", "quantity"], - } + }, ], } @@ -94,7 +94,7 @@ "Granulation", "Slough", "Necrotic", - ] + ], }, "description": {"type": "string"}, "push_score": {"type": "number"}, @@ -103,7 +103,7 @@ }, "additionalProperties": False, "required": [], - } + }, ], } @@ -120,7 +120,7 @@ }, "additionalProperties": False, "required": ["region", "scale"], - } + }, ], } @@ -136,7 +136,7 @@ }, "additionalProperties": False, "required": ["procedure", "description"], - } + }, ], } diff --git a/care/facility/models/mixins/permissions/asset.py b/care/facility/models/mixins/permissions/asset.py index 8affe28606..fa215cc807 100644 --- a/care/facility/models/mixins/permissions/asset.py +++ b/care/facility/models/mixins/permissions/asset.py @@ -7,7 +7,7 @@ class IsAssetUser: def has_permission(self, request, view): return bool( - request.user and request.user.is_authenticated and request.user.asset + request.user and request.user.is_authenticated and request.user.asset, ) def has_object_permission(self, request, view, obj): @@ -30,14 +30,11 @@ def has_object_read_permission(self, request): return True def has_object_write_permission(self, request): - if ( - request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"] - ): - return False - - return True + return request.user.user_type not in { + User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StateReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StaffReadOnly"], + } def has_object_update_permission(self, request): return self.has_object_write_permission(request) diff --git a/care/facility/models/mixins/permissions/base.py b/care/facility/models/mixins/permissions/base.py index 1b9056238a..5a4174d2f3 100644 --- a/care/facility/models/mixins/permissions/base.py +++ b/care/facility/models/mixins/permissions/base.py @@ -8,11 +8,11 @@ def has_read_permission(request): @staticmethod def has_write_permission(request): - if ( - request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"] - ): + if request.user.user_type in { + User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StateReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StaffReadOnly"], + }: return False return ( request.user.is_superuser @@ -36,11 +36,11 @@ def has_object_read_permission(self, request): ) def has_object_update_permission(self, request): - if ( - request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"] - ): + if request.user.user_type in { + User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StateReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StaffReadOnly"], + }: return False return (request.user.is_superuser) or ( (hasattr(self, "created_by") and request.user == self.created_by) @@ -57,11 +57,11 @@ def has_object_update_permission(self, request): ) def has_object_destroy_permission(self, request): - if ( - request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"] - ): + if request.user.user_type in { + User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StateReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StaffReadOnly"], + }: return False return request.user.is_superuser or ( hasattr(self, "created_by") and request.user == self.created_by diff --git a/care/facility/models/mixins/permissions/facility.py b/care/facility/models/mixins/permissions/facility.py index 468463822f..53b0bd2411 100644 --- a/care/facility/models/mixins/permissions/facility.py +++ b/care/facility/models/mixins/permissions/facility.py @@ -65,11 +65,11 @@ def has_object_read_permission(self, request): ) def has_object_write_permission(self, request): - if ( - request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"] - ): + if request.user.user_type in { + User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StateReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StaffReadOnly"], + }: return False if request.user.user_type < User.TYPE_VALUE_MAP["Staff"]: # todo Temporary return False @@ -77,7 +77,7 @@ def has_object_write_permission(self, request): def has_object_update_permission(self, request): return super().has_object_update_permission( - request + request, ) or self.has_object_write_permission(request) def has_object_destroy_permission(self, request): @@ -92,17 +92,17 @@ class FacilityRelatedPermissionMixin(BasePermissionMixin): def has_write_permission(request): from care.facility.models.facility import Facility - if ( - request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"] - ): + if request.user.user_type in { + User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StateReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StaffReadOnly"], + }: return False facility = False try: facility = Facility.objects.get( - external_id=request.parser_context["kwargs"]["facility_external_id"] + external_id=request.parser_context["kwargs"]["facility_external_id"], ) except Facility.DoesNotExist: return False @@ -129,11 +129,11 @@ def has_object_read_permission(self, request): ) def has_object_write_permission(self, request): - if ( - request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"] - ): + if request.user.user_type in { + User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StateReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StaffReadOnly"], + }: return False return ( super().has_write_permission(request) diff --git a/care/facility/models/mixins/permissions/patient.py b/care/facility/models/mixins/permissions/patient.py index f014b4f2a4..eae739e80c 100644 --- a/care/facility/models/mixins/permissions/patient.py +++ b/care/facility/models/mixins/permissions/patient.py @@ -7,11 +7,11 @@ class PatientPermissionMixin(BasePermissionMixin): def has_write_permission(request): if request.user.asset: return False - if ( - request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"] - ): + if request.user.user_type in { + User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StateReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StaffReadOnly"], + }: return False return ( request.user.is_superuser @@ -22,10 +22,10 @@ def has_write_permission(request): def has_object_read_permission(self, request): doctor_allowed = False if self.last_consultation: - doctor_allowed = ( - self.last_consultation.assigned_to == request.user - or request.user == self.assigned_to - ) + doctor_allowed = request.user in { + self.last_consultation.assigned_to, + self.assigned_to, + } return request.user.is_superuser or ( (hasattr(self, "created_by") and request.user == self.created_by) or ( @@ -57,15 +57,15 @@ def has_object_write_permission(self, request): return False doctor_allowed = False if self.last_consultation: - doctor_allowed = ( - self.last_consultation.assigned_to == request.user - or request.user == self.assigned_to - ) - if ( - request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"] - ): + doctor_allowed = request.user in { + self.last_consultation.assigned_to, + self.assigned_to, + } + if request.user.user_type in { + User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StateReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StaffReadOnly"], + }: return False return request.user.is_superuser or ( (hasattr(self, "created_by") and request.user == self.created_by) @@ -96,15 +96,15 @@ def has_object_update_permission(self, request): return False doctor_allowed = False if self.last_consultation: - doctor_allowed = ( - self.last_consultation.assigned_to == request.user - or request.user == self.assigned_to - ) - if ( - request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"] - ): + doctor_allowed = request.user in { + self.last_consultation.assigned_to, + self.assigned_to, + } + if request.user.user_type in { + User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StateReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StaffReadOnly"], + }: return False return ( request.user.is_superuser @@ -136,14 +136,14 @@ def has_object_icmr_sample_permission(self, request): def has_object_transfer_permission(self, request): if request.user.asset: return False - if ( - request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"] - ): + if request.user.user_type in { + User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StateReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StaffReadOnly"], + }: return False new_facility = Facility.objects.filter( - id=request.data.get("facility", None) + id=request.data.get("facility", None), ).first() return self.has_object_update_permission(request) or ( new_facility and request.user in new_facility.users.all() @@ -153,11 +153,11 @@ def has_object_transfer_permission(self, request): class PatientRelatedPermissionMixin(BasePermissionMixin): @staticmethod def has_write_permission(request): - if ( - request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"] - ): + if request.user.user_type in { + User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StateReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StaffReadOnly"], + }: return False return ( request.user.is_superuser @@ -173,8 +173,11 @@ def has_object_read_permission(self, request): and request.user in self.patient.facility.users.all() ) or ( - self.assigned_to == request.user - or request.user == self.patient.assigned_to + request.user + in { + self.patient.assigned_to, + self.assigned_to, + } ) or ( request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] @@ -193,11 +196,11 @@ def has_object_read_permission(self, request): ) def has_object_update_permission(self, request): - if ( - request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"] - ): + if request.user.user_type in { + User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StateReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StaffReadOnly"], + }: return False return ( request.user.is_superuser @@ -206,8 +209,11 @@ def has_object_update_permission(self, request): and self.patient.facility == request.user.home_facility ) or ( - self.assigned_to == request.user - or request.user == self.patient.assigned_to + request.user + in { + self.patient.assigned_to, + self.assigned_to, + } ) or ( request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] diff --git a/care/facility/models/notification.py b/care/facility/models/notification.py index de2b1d59d8..599395f57b 100644 --- a/care/facility/models/notification.py +++ b/care/facility/models/notification.py @@ -47,7 +47,8 @@ class Event(enum.Enum): related_name="notification_intended_for", ) medium_sent = models.IntegerField( - choices=MediumChoices, default=Medium.SYSTEM.value + choices=MediumChoices, + default=Medium.SYSTEM.value, ) caused_by = models.ForeignKey( User, @@ -57,7 +58,8 @@ class Event(enum.Enum): ) read_at = models.DateTimeField(null=True, blank=True) event_type = models.IntegerField( - choices=EventTypeChoices, default=EventType.SYSTEM_GENERATED.value + choices=EventTypeChoices, + default=EventType.SYSTEM_GENERATED.value, ) event = models.IntegerField(choices=EventChoices, default=Event.MESSAGE.value) message = models.TextField(max_length=2000, null=True, default=None) diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py index 44fe0ee1a6..9481549693 100644 --- a/care/facility/models/patient.py +++ b/care/facility/models/patient.py @@ -1,9 +1,9 @@ -import datetime import enum from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.db.models import JSONField +from django.utils import timezone from simple_history.models import HistoricalRecords from care.abdm.models import AbhaNumber @@ -39,6 +39,14 @@ from care.utils.models.validators import mobile_or_landline_number_validator +def format_as_date(date): + return date.strftime("%d/%m/%Y") + + +def format_as_time(time): + return time.strftime("%H:%M") + + class PatientRegistration(PatientBaseModel, PatientPermissionMixin): # fields in the PatientSearch model PATIENT_SEARCH_KEYS = [ @@ -57,7 +65,7 @@ class SourceEnum(enum.Enum): SourceChoices = [(e.value, e.name) for e in SourceEnum] - class vaccineEnum(enum.Enum): + class VaccineEnum(enum.Enum): COVISHIELD = "CoviShield" COVAXIN = "Covaxin" SPUTNIK = "Sputnik" @@ -66,7 +74,7 @@ class vaccineEnum(enum.Enum): JANSSEN = "Janssen" SINOVAC = "Sinovac" - vaccineChoices = [(e.value, e.name) for e in vaccineEnum] + VaccineChoices = [(e.value, e.name) for e in VaccineEnum] class ActionEnum(enum.Enum): NO_ACTION = 10 @@ -100,7 +108,9 @@ class TestTypeEnum(enum.Enum): related_name="nearest_facility", ) meta_info = models.OneToOneField( - "PatientMetaInfo", on_delete=models.SET_NULL, null=True + "PatientMetaInfo", + on_delete=models.SET_NULL, + null=True, ) # name_old = EncryptedCharField(max_length=200, default="") @@ -111,11 +121,15 @@ class TestTypeEnum(enum.Enum): # phone_number_old = EncryptedCharField(max_length=14, validators=[phone_number_regex], default="") phone_number = models.CharField( - max_length=14, validators=[mobile_or_landline_number_validator], default="" + max_length=14, + validators=[mobile_or_landline_number_validator], + default="", ) emergency_phone_number = models.CharField( - max_length=14, validators=[mobile_or_landline_number_validator], default="" + max_length=14, + validators=[mobile_or_landline_number_validator], + default="", ) # address_old = EncryptedTextField(default="") @@ -128,7 +142,9 @@ class TestTypeEnum(enum.Enum): year_of_birth = models.IntegerField(default=0, null=True) nationality = models.CharField( - max_length=255, default="", verbose_name="Nationality of Patient" + max_length=255, + default="", + verbose_name="Nationality of Patient", ) passport_no = models.CharField( max_length=255, @@ -138,7 +154,8 @@ class TestTypeEnum(enum.Enum): # aadhar_no = models.CharField(max_length=255, default="", verbose_name="Aadhar Number of Patient") is_medical_worker = models.BooleanField( - default=False, verbose_name="Is the Patient a Medical Worker" + default=False, + verbose_name="Is the Patient a Medical Worker", ) blood_group = models.CharField( @@ -150,10 +167,12 @@ class TestTypeEnum(enum.Enum): ) contact_with_confirmed_carrier = models.BooleanField( - default=False, verbose_name="Confirmed Contact with a Covid19 Carrier" + default=False, + verbose_name="Confirmed Contact with a Covid19 Carrier", ) contact_with_suspected_carrier = models.BooleanField( - default=False, verbose_name="Suspected Contact with a Covid19 Carrier" + default=False, + verbose_name="Suspected Contact with a Covid19 Carrier", ) estimated_contact_date = models.DateTimeField(null=True, blank=True) @@ -179,35 +198,50 @@ class TestTypeEnum(enum.Enum): ) allergies = models.TextField( - default="", blank=True, verbose_name="Patient's Known Allergies" + default="", + blank=True, + verbose_name="Patient's Known Allergies", ) present_health = models.TextField( - default="", blank=True, verbose_name="Patient's Current Health Details" + default="", + blank=True, + verbose_name="Patient's Current Health Details", ) ongoing_medication = models.TextField( default="", blank=True, verbose_name="Already pescribed medication if any", ) - has_SARI = models.BooleanField( - default=False, verbose_name="Does the Patient Suffer from SARI" + has_SARI = models.BooleanField( # noqa: N815 + default=False, + verbose_name="Does the Patient Suffer from SARI", ) is_antenatal = models.BooleanField( - default=None, verbose_name="Does the patient require Prenatal Care ?" + default=None, + verbose_name="Does the patient require Prenatal Care ?", ) ward_old = models.CharField( - max_length=255, default="", verbose_name="Ward of Patient", blank=False + max_length=255, + default="", + verbose_name="Ward of Patient", + blank=False, ) ward = models.ForeignKey(Ward, on_delete=models.SET_NULL, null=True, blank=True) local_body = models.ForeignKey( - LocalBody, on_delete=models.SET_NULL, null=True, blank=True + LocalBody, + on_delete=models.SET_NULL, + null=True, + blank=True, ) district = models.ForeignKey( - District, on_delete=models.SET_NULL, null=True, blank=True + District, + on_delete=models.SET_NULL, + null=True, + blank=True, ) state = models.ForeignKey(State, on_delete=models.SET_NULL, null=True, blank=True) @@ -242,10 +276,15 @@ class TestTypeEnum(enum.Enum): ) action = models.IntegerField( - choices=ActionChoices, blank=True, null=True, default=ActionEnum.NO_ACTION.value + choices=ActionChoices, + blank=True, + null=True, + default=ActionEnum.NO_ACTION.value, ) review_time = models.DateTimeField( - null=True, blank=True, verbose_name="Patient's next review time" + null=True, + blank=True, + verbose_name="Patient's next review time", ) created_by = models.ForeignKey( @@ -269,17 +308,23 @@ class TestTypeEnum(enum.Enum): # Issue #600 Care_Fe date_of_test = models.DateTimeField( - null=True, blank=True, verbose_name="Patient's test Date" + null=True, + blank=True, + verbose_name="Patient's test Date", ) srf_id = models.CharField(max_length=200, blank=True, default="") test_type = models.IntegerField( - choices=TestTypeChoices, default=TestTypeEnum.UNK.value + choices=TestTypeChoices, + default=TestTypeEnum.UNK.value, ) allow_transfer = models.BooleanField(default=False) last_consultation = models.ForeignKey( - PatientConsultation, on_delete=models.SET_NULL, null=True, default=None + PatientConsultation, + on_delete=models.SET_NULL, + null=True, + default=None, ) will_donate_blood = models.BooleanField( @@ -362,7 +407,7 @@ class TestTypeEnum(enum.Enum): validators=[MinValueValidator(0), MaxValueValidator(3)], ) vaccine_name = models.CharField( - choices=vaccineChoices, + choices=VaccineChoices, default=None, null=True, blank=False, @@ -377,7 +422,9 @@ class TestTypeEnum(enum.Enum): verbose_name="COVID-19 Vaccination ID", ) last_vaccinated_date = models.DateTimeField( - null=True, blank=True, verbose_name="Date Last Vaccinated" + null=True, + blank=True, + verbose_name="Date Last Vaccinated", ) # Extras @@ -394,7 +441,9 @@ class TestTypeEnum(enum.Enum): verbose_name="Is Patient Declared Positive", ) date_declared_positive = models.DateTimeField( - null=True, blank=True, verbose_name="Date Patient is Declared Positive" + null=True, + blank=True, + verbose_name="Date Patient is Declared Positive", ) # Permission Scopes @@ -409,7 +458,10 @@ class TestTypeEnum(enum.Enum): # ABDM Health ID abha_number = models.OneToOneField( - AbhaNumber, on_delete=models.SET_NULL, null=True, blank=True + AbhaNumber, + on_delete=models.SET_NULL, + null=True, + blank=True, ) history = HistoricalRecords(excluded_fields=["meta_info"]) @@ -417,7 +469,7 @@ class TestTypeEnum(enum.Enum): objects = BaseManager() def __str__(self): - return "{} - {} - {}".format(self.name, self.age, self.get_gender_display()) + return f"{self.name} - {self.age} - {self.get_gender_display()}" @property def tele_consultation_history(self): @@ -452,10 +504,10 @@ def save(self, *args, **kwargs) -> None: self.year_of_birth = ( self.date_of_birth.year if self.date_of_birth is not None - else datetime.datetime.now().year - self.age + else timezone.now().year - self.age ) - today = datetime.date.today() + today = timezone.now().date() if self.date_of_birth: self.age = ( @@ -472,7 +524,7 @@ def save(self, *args, **kwargs) -> None: self.date_of_receipt_of_information = ( self.date_of_receipt_of_information if self.date_of_receipt_of_information is not None - else datetime.datetime.now() + else timezone.now() ) self._alias_recovery_to_recovered() @@ -498,12 +550,6 @@ def save(self, *args, **kwargs) -> None: "last_consultation__discharge_date__time": "Time of discharge", } - def format_as_date(date): - return date.strftime("%d/%m/%Y") - - def format_as_time(time): - return time.strftime("%H:%M") - CSV_MAKE_PRETTY = { "gender": (lambda x: REVERSE_GENDER_CHOICES[x]), "created_date": format_as_date, @@ -546,6 +592,9 @@ class OccupationEnum(enum.Enum): occupation = models.IntegerField(choices=OccupationChoices) head_of_household = models.BooleanField() + def __str__(self): + return self.get_occupation_display() + class PatientContactDetails(models.Model): class RelationEnum(enum.IntEnum): @@ -603,13 +652,16 @@ class ModeOfContactEnum(enum.IntEnum): is_primary = models.BooleanField(help_text="If false, then secondary contact") condition_of_contact_is_symptomatic = models.BooleanField( - help_text="While in contact, did the patient showing symptoms" + help_text="While in contact, did the patient showing symptoms", ) deleted = models.BooleanField(default=False) objects = BaseManager() + def __str__(self): + return f"{self.patient} - {self.patient_in_contact} - {self.get_relation_with_patient_display()}" + class Disease(models.Model): patient = models.ForeignKey( @@ -629,7 +681,7 @@ class Meta: fields=["patient", "disease"], condition=models.Q(deleted=False), name="unique_patient_disease", - ) + ), ] def __str__(self): @@ -669,17 +721,24 @@ class Meta: class PatientMobileOTP(BaseModel): is_used = models.BooleanField(default=False) phone_number = models.CharField( - max_length=14, validators=[mobile_or_landline_number_validator] + max_length=14, + validators=[mobile_or_landline_number_validator], ) otp = models.CharField(max_length=10) class PatientNotes(FacilityBaseModel, PatientRelatedPermissionMixin): patient = models.ForeignKey( - PatientRegistration, on_delete=models.PROTECT, null=False, blank=False + PatientRegistration, + on_delete=models.PROTECT, + null=False, + blank=False, ) facility = models.ForeignKey( - Facility, on_delete=models.PROTECT, null=False, blank=False + Facility, + on_delete=models.PROTECT, + null=False, + blank=False, ) user_type = models.CharField(max_length=25, default="") created_by = models.ForeignKey( diff --git a/care/facility/models/patient_consultation.py b/care/facility/models/patient_consultation.py index c43f6fd5da..33eb306901 100644 --- a/care/facility/models/patient_consultation.py +++ b/care/facility/models/patient_consultation.py @@ -55,17 +55,25 @@ class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): ) facility = models.ForeignKey( - "Facility", on_delete=models.CASCADE, related_name="consultations" + "Facility", + on_delete=models.CASCADE, + related_name="consultations", ) diagnosis = models.TextField(default="", null=True, blank=True) # Deprecated icd11_provisional_diagnoses = ArrayField( - models.CharField(max_length=100), default=list, blank=True, null=True + models.CharField(max_length=100), + default=list, + blank=True, + null=True, ) icd11_diagnoses = ArrayField( - models.CharField(max_length=100), default=list, blank=True, null=True + models.CharField(max_length=100), + default=list, + blank=True, + null=True, ) icd11_principal_diagnosis = models.CharField( - max_length=100, default="", blank=True, null=True + max_length=100, default="", blank=True, null=True, ) symptoms = MultiSelectField( choices=SYMPTOM_CHOICES, @@ -84,7 +92,10 @@ class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): null=True, ) # Deprecated category = models.CharField( - choices=CATEGORY_CHOICES, max_length=8, blank=False, null=True + choices=CATEGORY_CHOICES, + max_length=8, + blank=False, + null=True, ) examination_details = models.TextField(null=True, blank=True) history_of_present_illness = models.TextField(null=True, blank=True) @@ -121,10 +132,14 @@ class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): ) discharge_notes = models.TextField(default="", null=True, blank=True) discharge_prescription = JSONField( - default=dict, null=True, blank=True + default=dict, + null=True, + blank=True, ) # Deprecated discharge_prn_prescription = JSONField( - default=dict, null=True, blank=True + default=dict, + null=True, + blank=True, ) # Deprecated death_datetime = models.DateTimeField(null=True, blank=True) death_confirmed_doctor = models.TextField(default="", null=True, blank=True) @@ -145,11 +160,14 @@ class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): deprecated_verified_by = models.TextField(default="", null=True, blank=True) verified_by = models.ForeignKey( - User, on_delete=models.SET_NULL, null=True, blank=True + User, on_delete=models.SET_NULL, null=True, blank=True, ) created_by = models.ForeignKey( - User, on_delete=models.SET_NULL, null=True, related_name="created_user" + User, + on_delete=models.SET_NULL, + null=True, + related_name="created_user", ) last_edited_by = models.ForeignKey( @@ -224,29 +242,9 @@ class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): ), } - # CSV_DATATYPE_DEFAULT_MAPPING = { - # "admission_date": (None, models.DateTimeField(),), - # "symptoms_onset_date": (None, models.DateTimeField(),), - # "symptoms": ("-", models.CharField(),), - # "category": ("-", models.CharField(),), - # "examination_details": ("-", models.CharField(),), - # "suggestion": ("-", models.CharField(),), - # } - def __str__(self): return f"{self.patient.name}<>{self.facility.name}" - def save(self, *args, **kwargs): - """ - # Removing Patient Hospital Change on Referral - if not self.pk or self.referred_to is not None: - # pk is None when the consultation is created - # referred to is not null when the person is being referred to a new facility - self.patient.facility = self.referred_to or self.facility - self.patient.save() - """ - super(PatientConsultation, self).save(*args, **kwargs) - class Meta: constraints = [ models.CheckConstraint( diff --git a/care/facility/models/patient_external_test.py b/care/facility/models/patient_external_test.py index 1e13c8f5c2..4bf1a10aef 100644 --- a/care/facility/models/patient_external_test.py +++ b/care/facility/models/patient_external_test.py @@ -16,10 +16,16 @@ class PatientExternalTest(FacilityBaseModel): patient_status = models.CharField(max_length=15) ward = models.ForeignKey(Ward, on_delete=models.PROTECT, null=True, blank=True) local_body = models.ForeignKey( - LocalBody, on_delete=models.PROTECT, null=False, blank=False + LocalBody, + on_delete=models.PROTECT, + null=False, + blank=False, ) district = models.ForeignKey( - District, on_delete=models.PROTECT, null=False, blank=False + District, + on_delete=models.PROTECT, + null=False, + blank=False, ) source = models.CharField(max_length=255, blank=True, null=True) patient_category = models.CharField(max_length=255, blank=True, null=True) @@ -78,7 +84,7 @@ class PatientExternalTest(FacilityBaseModel): "sample_type": "Sample Type", "result": "Final Result", "sample_collection_date": "Sample Collection Date", - "source": "Source" + "source": "Source", # "result_date": "", } diff --git a/care/facility/models/patient_icmr.py b/care/facility/models/patient_icmr.py index e2f0ac9752..4100677af7 100644 --- a/care/facility/models/patient_icmr.py +++ b/care/facility/models/patient_icmr.py @@ -1,6 +1,7 @@ import datetime from dateutil.relativedelta import relativedelta +from django.utils import timezone from care.facility.models import ( DISEASE_CHOICES_MAP, @@ -48,11 +49,16 @@ class Meta: @property def age_years(self): if self.date_of_birth is not None: - age_years = relativedelta(datetime.datetime.now(), self.date_of_birth).years + age_years = relativedelta(timezone.now(), self.date_of_birth).years else: age_years = relativedelta( - datetime.datetime.now(), - datetime.datetime(year=self.year_of_birth, month=1, day=1), + timezone.now(), + datetime.datetime( + year=self.year_of_birth, + month=1, + day=1, + tzinfo=timezone.get_default_timezone(), + ), ).years return age_years @@ -62,7 +68,8 @@ def age_months(self): age_months = 0 else: age_months = relativedelta( - datetime.datetime.now(), self.date_of_birth + timezone.now(), + self.date_of_birth, ).months return age_months @@ -87,9 +94,10 @@ def has_travel_to_foreign_last_14_days(self): if self.countries_travelled: return len(self.countries_travelled) != 0 and ( self.date_of_return - and (self.date_of_return.date() - datetime.datetime.now().date()).days - <= 14 + and (self.date_of_return.date() - timezone.now().date()).days + <= 14 # noqa: PLR2004 ) + return None @property def travel_end_date(self): @@ -214,13 +222,10 @@ class Meta: proxy = True def is_symptomatic(self): - if ( - SYMPTOM_CHOICES[0][0] not in self.symptoms.choices.keys() - or self.symptoms_onset_date is not None - ): - return True - else: - return False + return bool( + SYMPTOM_CHOICES[0][0] not in self.symptoms.choices + or self.symptoms_onset_date is not None, + ) def symptomatic_international_traveller( self, @@ -230,12 +235,10 @@ def symptomatic_international_traveller( and len(self.patient.countries_travelled) != 0 and ( self.patient.date_of_return - and ( - self.patient.date_of_return.date() - datetime.datetime.now().date() - ).days - <= 14 + and (self.patient.date_of_return.date() - timezone.now().date()).days + <= 14 # noqa: PLR2004 ) - and self.is_symptomatic() + and self.is_symptomatic(), ) def symptomatic_contact_of_confirmed_case( diff --git a/care/facility/models/patient_investigation.py b/care/facility/models/patient_investigation.py index 9e94e23153..d5f6e8b5eb 100644 --- a/care/facility/models/patient_investigation.py +++ b/care/facility/models/patient_investigation.py @@ -24,7 +24,11 @@ class PatientInvestigation(BaseModel): min_value = models.FloatField(blank=True, default=None, null=True) max_value = models.FloatField(blank=True, default=None, null=True) investigation_type = models.CharField( - max_length=10, choices=TestTypeChoices, blank=False, null=True, default=None + max_length=10, + choices=TestTypeChoices, + blank=False, + null=True, + default=None, ) choices = models.TextField(null=True, blank=True) @@ -34,10 +38,15 @@ def __str__(self) -> str: class InvestigationSession(BaseModel): external_id = models.UUIDField( - default=uuid4, unique=True, db_index=True + default=uuid4, + unique=True, + db_index=True, ) # session_id created_by = models.ForeignKey( - User, null=False, blank=False, on_delete=models.PROTECT + User, + null=False, + blank=False, + on_delete=models.PROTECT, ) class Meta: @@ -45,23 +54,35 @@ class Meta: models.Index( fields=[ "-created_date", - ] + ], ), ] class InvestigationValue(BaseModel): investigation = models.ForeignKey( - PatientInvestigation, on_delete=models.PROTECT, blank=False, null=False + PatientInvestigation, + on_delete=models.PROTECT, + blank=False, + null=False, ) group = models.ForeignKey( - PatientInvestigationGroup, on_delete=models.PROTECT, blank=True, null=True + PatientInvestigationGroup, + on_delete=models.PROTECT, + blank=True, + null=True, ) value = models.FloatField(blank=True, null=True, default=None) notes = models.TextField(blank=True, null=True, default=None) consultation = models.ForeignKey( - PatientConsultation, on_delete=models.PROTECT, blank=False, null=False + PatientConsultation, + on_delete=models.PROTECT, + blank=False, + null=False, ) session = models.ForeignKey( - InvestigationSession, on_delete=models.PROTECT, blank=False, null=False + InvestigationSession, + on_delete=models.PROTECT, + blank=False, + null=False, ) diff --git a/care/facility/models/patient_sample.py b/care/facility/models/patient_sample.py index a3a7405521..8db8a9a8b2 100644 --- a/care/facility/models/patient_sample.py +++ b/care/facility/models/patient_sample.py @@ -93,7 +93,8 @@ class PatientSample(FacilityBaseModel): default=SAMPLE_TEST_FLOW_MAP["REQUEST_SUBMITTED"], ) result = models.IntegerField( - choices=SAMPLE_TEST_RESULT_CHOICES, default=SAMPLE_TEST_RESULT_MAP["AWAITING"] + choices=SAMPLE_TEST_RESULT_CHOICES, + default=SAMPLE_TEST_RESULT_MAP["AWAITING"], ) fast_track = models.TextField(default="") @@ -102,14 +103,23 @@ class PatientSample(FacilityBaseModel): date_of_result = models.DateTimeField(null=True, blank=True) testing_facility = models.ForeignKey( - "Facility", on_delete=models.SET_NULL, null=True, blank=True + "Facility", + on_delete=models.SET_NULL, + null=True, + blank=True, ) created_by = models.ForeignKey( - User, on_delete=models.SET_NULL, null=True, related_name="samples_created" + User, + on_delete=models.SET_NULL, + null=True, + related_name="samples_created", ) last_edited_by = models.ForeignKey( - User, on_delete=models.SET_NULL, null=True, related_name="last_edited_by" + User, + on_delete=models.SET_NULL, + null=True, + related_name="last_edited_by", ) CSV_MAPPING = { @@ -152,11 +162,11 @@ def flow(self): @staticmethod def has_write_permission(request): - if ( - request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"] - ): + if request.user.user_type in { + User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StateReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StaffReadOnly"], + }: return False return ( request.user.is_superuser @@ -190,11 +200,11 @@ def has_object_read_permission(self, request): ) def has_object_update_permission(self, request): - if ( - request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"] - ): + if request.user.user_type in { + User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StateReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StaffReadOnly"], + }: return False if not self.has_object_read_permission(request): return False diff --git a/care/facility/models/patient_tele_consultation.py b/care/facility/models/patient_tele_consultation.py index 148af50990..0be34425b0 100644 --- a/care/facility/models/patient_tele_consultation.py +++ b/care/facility/models/patient_tele_consultation.py @@ -9,7 +9,8 @@ class PatientTeleConsultation(models.Model): patient = models.ForeignKey(PatientRegistration, on_delete=models.PROTECT) symptoms = MultiSelectField( - choices=SYMPTOM_CHOICES, max_length=get_max_length(SYMPTOM_CHOICES, None) + choices=SYMPTOM_CHOICES, + max_length=get_max_length(SYMPTOM_CHOICES, None), ) other_symptoms = models.TextField(blank=True, null=True) reason = models.TextField(blank=True, null=True, verbose_name="Reason for calling") diff --git a/care/facility/models/prescription.py b/care/facility/models/prescription.py index b6aca21eab..40621ce7e2 100644 --- a/care/facility/models/prescription.py +++ b/care/facility/models/prescription.py @@ -50,7 +50,7 @@ class MedibaseMedicine(BaseModel): db_index=True, unique=True, ) - type = models.CharField( + type = models.CharField( # noqa: A003 max_length=16, choices=generate_choices(MedibaseMedicineType), blank=False, @@ -122,7 +122,7 @@ class Prescription(BaseModel): discontinued_date = models.DateTimeField(null=True, blank=True) is_migrated = models.BooleanField( - default=False + default=False, ) # This field is to throw caution to data that was previously ported over def save(self, *args, **kwargs) -> None: @@ -154,7 +154,9 @@ class MedicineAdministration(BaseModel): on_delete=models.PROTECT, ) administered_date = models.DateTimeField( - null=False, blank=False, default=timezone.now + null=False, + blank=False, + default=timezone.now, ) def __str__(self): @@ -167,7 +169,7 @@ def __str__(self): def validate(self) -> None: if self.prescription.discontinued: raise ValidationError( - {"prescription": "Prescription has been discontinued."} + {"prescription": "Prescription has been discontinued."}, ) def save(self, *args, **kwargs) -> None: diff --git a/care/facility/models/prescription_supplier.py b/care/facility/models/prescription_supplier.py index 434bbe67e1..121862fc97 100644 --- a/care/facility/models/prescription_supplier.py +++ b/care/facility/models/prescription_supplier.py @@ -29,15 +29,24 @@ class StatusEnum(enum.Enum): related_name="patient_consultation", ) scheme = models.IntegerField( - choices=SchemeChoices, default=10, null=False, blank=False + choices=SchemeChoices, + default=10, + null=False, + blank=False, ) status = models.IntegerField( - choices=StatusChoices, default=10, null=False, blank=False + choices=StatusChoices, + default=10, + null=False, + blank=False, ) supplier = models.TextField(default="", blank=True) remarks = models.TextField(default="", blank=True) updated_user = models.ForeignKey( - User, on_delete=models.SET_NULL, null=True, blank=True + User, + on_delete=models.SET_NULL, + null=True, + blank=True, ) def has_object_read_permission(self, request): @@ -59,10 +68,10 @@ def has_object_read_permission(self, request): ) def has_object_write_permission(self, request): - if ( - request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"] - or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"] - ): + if request.user.user_type in { + User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StateReadOnlyAdmin"], + User.TYPE_VALUE_MAP["StaffReadOnly"], + }: return False return self.has_object_read_permission(request) diff --git a/care/facility/models/resources.py b/care/facility/models/resources.py index 492e893aff..1af45c5b84 100644 --- a/care/facility/models/resources.py +++ b/care/facility/models/resources.py @@ -65,13 +65,22 @@ class ResourceRequest(FacilityBaseModel): blank=True, ) status = models.IntegerField( - choices=RESOURCE_STATUS_CHOICES, default=10, null=False, blank=False + choices=RESOURCE_STATUS_CHOICES, + default=10, + null=False, + blank=False, ) category = models.IntegerField( - choices=RESOURCE_CATEGORY_CHOICES, default=100, null=False, blank=False + choices=RESOURCE_CATEGORY_CHOICES, + default=100, + null=False, + blank=False, ) sub_category = models.IntegerField( - choices=RESOURCE_SUB_CATEGORY_CHOICES, default=1000, null=False, blank=False + choices=RESOURCE_SUB_CATEGORY_CHOICES, + default=1000, + null=False, + blank=False, ) priority = models.IntegerField(default=None, null=True, blank=True) @@ -157,7 +166,10 @@ def has_object_update_permission(self, request): class ResourceRequestComment(FacilityBaseModel): request = models.ForeignKey( - ResourceRequest, on_delete=models.PROTECT, null=False, blank=False + ResourceRequest, + on_delete=models.PROTECT, + null=False, + blank=False, ) created_by = models.ForeignKey( User, diff --git a/care/facility/models/shifting.py b/care/facility/models/shifting.py index 4b88925b60..e55bfe858c 100644 --- a/care/facility/models/shifting.py +++ b/care/facility/models/shifting.py @@ -57,7 +57,10 @@ class ShiftingRequest(FacilityBaseModel): related_name="shifting_approving_facility", ) assigned_facility_type = models.IntegerField( - choices=FACILITY_TYPES, default=None, null=True, blank=True + choices=FACILITY_TYPES, + default=None, + null=True, + blank=True, ) assigned_facility = models.ForeignKey( "Facility", @@ -67,14 +70,19 @@ class ShiftingRequest(FacilityBaseModel): ) assigned_facility_external = models.TextField(default="", null=True, blank=True) patient = models.ForeignKey( - "PatientRegistration", on_delete=models.CASCADE, related_name="patient" + "PatientRegistration", + on_delete=models.CASCADE, + related_name="patient", ) emergency = models.BooleanField(default=False) is_up_shift = models.BooleanField(default=False) # False for Down , True for UP reason = models.TextField(default="", blank=True) vehicle_preference = models.TextField(default="", blank=True) preferred_vehicle_choice = models.IntegerField( - choices=VEHICLE_CHOICES, default=None, null=True, blank=True + choices=VEHICLE_CHOICES, + default=None, + null=True, + blank=True, ) comments = models.TextField(default="", blank=True) refering_facility_contact_name = models.TextField(default="", blank=True) @@ -86,11 +94,17 @@ class ShiftingRequest(FacilityBaseModel): ) is_kasp = models.BooleanField(default=False) status = models.IntegerField( - choices=SHIFTING_STATUS_CHOICES, default=10, null=False, blank=False + choices=SHIFTING_STATUS_CHOICES, + default=10, + null=False, + blank=False, ) breathlessness_level = models.IntegerField( - choices=BREATHLESSNESS_CHOICES, default=10, null=True, blank=True + choices=BREATHLESSNESS_CHOICES, + default=10, + null=True, + blank=True, ) is_assigned_to_user = models.BooleanField(default=False) @@ -179,7 +193,10 @@ def has_object_update_permission(self, request): class ShiftingRequestComment(FacilityBaseModel): request = models.ForeignKey( - ShiftingRequest, on_delete=models.PROTECT, null=False, blank=False + ShiftingRequest, + on_delete=models.PROTECT, + null=False, + blank=False, ) created_by = models.ForeignKey( User, diff --git a/care/facility/models/summary.py b/care/facility/models/summary.py index 5579bc564d..28a0debe2b 100644 --- a/care/facility/models/summary.py +++ b/care/facility/models/summary.py @@ -19,7 +19,10 @@ class FacilityRelatedSummary(models.Model): created_date = models.DateTimeField(auto_now_add=True, null=True, blank=True) modified_date = models.DateTimeField(auto_now=True, null=True, blank=True) facility = models.ForeignKey( - Facility, on_delete=models.CASCADE, null=True, blank=True + Facility, + on_delete=models.CASCADE, + null=True, + blank=True, ) s_type = models.CharField(choices=SUMMARY_CHOICES, max_length=100) data = JSONField(null=True, blank=True, default=dict) @@ -29,21 +32,24 @@ class Meta: models.Index( fields=[ "-modified_date", - ] + ], ), models.Index( fields=[ "-created_date", - ] + ], ), models.Index( fields=[ "s_type", - ] + ], ), models.Index(fields=["-created_date", "s_type"]), ] + def __str__(self): + return f"{self.facility.name} - {self.created_date}" + DISTRICT_SUMMARY_CHOICES = (("PatientSummary", "PatientSummary"),) @@ -53,7 +59,10 @@ class DistrictScopedSummary(models.Model): created_date = models.DateTimeField(auto_now_add=True, null=True, blank=True) modified_date = models.DateTimeField(auto_now=True, null=True, blank=True) district = models.ForeignKey( - District, on_delete=models.CASCADE, null=True, blank=True + District, + on_delete=models.CASCADE, + null=True, + blank=True, ) s_type = models.CharField(choices=DISTRICT_SUMMARY_CHOICES, max_length=100) data = JSONField(null=True, blank=True, default=dict) @@ -63,21 +72,24 @@ class Meta: models.Index( fields=[ "-modified_date", - ] + ], ), models.Index( fields=[ "-created_date", - ] + ], ), models.Index( fields=[ "s_type", - ] + ], ), models.Index(fields=["-created_date", "s_type"]), ] + def __str__(self): + return f"{self.district.name} - {self.created_date}" + LSG_SUMMARY_CHOICES = (("PatientSummary", "PatientSummary"),) @@ -95,17 +107,20 @@ class Meta: models.Index( fields=[ "-modified_date", - ] + ], ), models.Index( fields=[ "-created_date", - ] + ], ), models.Index( fields=[ "s_type", - ] + ], ), models.Index(fields=["-created_date", "s_type"]), ] + + def __str__(self): + return f"{self.lsg.name} - {self.created_date}" diff --git a/care/facility/models/tests/test_patient.py b/care/facility/models/tests/test_patient.py deleted file mode 100644 index d00b3e8d93..0000000000 --- a/care/facility/models/tests/test_patient.py +++ /dev/null @@ -1,25 +0,0 @@ -from rest_framework.test import APITestCase - -from care.facility.models import DiseaseStatusEnum -from care.utils.tests.test_utils import TestUtils - - -class PatientRegistrationTest(TestUtils, APITestCase): - @classmethod - def setUpTestData(cls): - cls.state = cls.create_state() - cls.district = cls.create_district(cls.state) - cls.local_body = cls.create_local_body(cls.district) - cls.super_user = cls.create_super_user("su", cls.district) - cls.facility = cls.create_facility(cls.super_user, cls.district, cls.local_body) - cls.user = cls.create_user("staff1", cls.district, home_facility=cls.facility) - cls.patient = cls.create_patient(cls.district, cls.facility) - - def test_disease_state_recovery_is_aliased_to_recovered(self): - patient = self.patient - - patient.disease_status = DiseaseStatusEnum.RECOVERY.value - patient.save(update_fields=["disease_status"]) - patient.refresh_from_db() - - self.assertEqual(patient.disease_status, DiseaseStatusEnum.RECOVERED.value) diff --git a/care/facility/static_data/icd11.py b/care/facility/static_data/icd11.py index b535acd70c..f19bd28f26 100644 --- a/care/facility/static_data/icd11.py +++ b/care/facility/static_data/icd11.py @@ -5,7 +5,7 @@ def fetch_data(): - with open("data/icd11.json", "r") as json_file: + with open("data/icd11.json") as json_file: return json.load(json_file) diff --git a/care/facility/static_data/medibase.py b/care/facility/static_data/medibase.py index fb5718c3df..1868ee17a8 100644 --- a/care/facility/static_data/medibase.py +++ b/care/facility/static_data/medibase.py @@ -12,10 +12,14 @@ def load_medibase_in_memory(): company_pretty=Coalesce("company", Value(""), output_field=CharField()), contents_pretty=Coalesce("contents", Value(""), output_field=TextField()), cims_class_pretty=Coalesce( - "cims_class", Value(""), output_field=CharField() + "cims_class", + Value(""), + output_field=CharField(), ), atc_classification_pretty=Coalesce( - "atc_classification", Value(""), output_field=TextField() + "atc_classification", + Value(""), + output_field=TextField(), ), ) .values_list( diff --git a/care/facility/tasks/asset_monitor.py b/care/facility/tasks/asset_monitor.py index 3c0b8e2fbe..194c35cb60 100644 --- a/care/facility/tasks/asset_monitor.py +++ b/care/facility/tasks/asset_monitor.py @@ -18,8 +18,8 @@ @shared_task -def check_asset_status(): - logger.info(f"Checking Asset Status: {timezone.now()}") +def check_asset_status(): # noqa: PLR0912 + logger.info("Checking Asset Status: %s", timezone.now().isoformat()) assets = Asset.objects.all() middleware_status_cache = {} @@ -54,41 +54,49 @@ def check_asset_status(): { **asset.meta, "middleware_hostname": hostname, - } + }, ) # Fetching the status of the device if asset.asset_class == "ONVIF": similar_assets = Asset.objects.filter( - asset_class="ONVIF" + asset_class="ONVIF", ).filter( Q(meta__middleware_hostname=hostname) - | Q(current_location__facility__middleware_address=hostname) + | Q( + current_location__facility__middleware_address=hostname, + ), ) assets_config = [] - for asset in similar_assets: + for similar_asset in similar_assets: try: - asset_config = asset.meta["camera_access_key"].split( - ":" - ) + asset_config = similar_asset.meta[ + "camera_access_key" + ].split(":") assets_config.append( { - "hostname": asset.meta.get("local_ip_address"), + "hostname": similar_asset.meta.get( + "local_ip_address", + ), "port": 80, "username": asset_config[0], "password": asset_config[1], - } + }, ) except Exception: - pass + logger.error( + "failed to get camera access key: %s", + similar_asset.id, + ) result = asset_class.api_post( - asset_class.get_url("cameras/status"), data=assets_config + asset_class.get_url("cameras/status"), + data=assets_config, ) else: result = asset_class.api_get( - asset_class.get_url("devices/status") + asset_class.get_url("devices/status"), ) except Exception: - logger.warn(f"Middleware {hostname} is down", exc_info=True) + logger.warning("Middleware %s is down", hostname, exc_info=True) # If no status is returned, setting default status as down if not result: @@ -102,8 +110,9 @@ def check_asset_status(): # Setting new status as down by default new_status = AvailabilityStatus.DOWN for status_record in result: - if asset.meta.get("local_ip_address") in status_record.get( - "status", {} + if isinstance(asset.meta, dict) and ( + asset.meta.get("local_ip_address") + in status_record.get("status", {}) ): asset_status = status_record["status"][ asset.meta.get("local_ip_address") @@ -137,4 +146,4 @@ def check_asset_status(): ) except Exception: - logger.error("Error in Asset Status Check", exc_info=True) + logger.exception("Error in Asset Status Check") diff --git a/care/facility/tasks/discharge_summary.py b/care/facility/tasks/discharge_summary.py index bbcdc974c8..36f4b789c3 100644 --- a/care/facility/tasks/discharge_summary.py +++ b/care/facility/tasks/discharge_summary.py @@ -12,29 +12,31 @@ email_discharge_summary, generate_and_upload_discharge_summary, ) -from care.utils.exceptions import CeleryTaskException +from care.utils.exceptions import CeleryTaskError logger: Logger = get_task_logger(__name__) @shared_task( - autoretry_for=(ClientError,), retry_kwargs={"max_retries": 3}, expires=10 * 60 + autoretry_for=(ClientError,), + retry_kwargs={"max_retries": 3}, + expires=10 * 60, ) def generate_discharge_summary_task(consultation_ext_id: str): """ Generate and Upload the Discharge Summary """ - logger.info(f"Generating Discharge Summary for {consultation_ext_id}") + logger.info("Generating Discharge Summary for %s", consultation_ext_id) try: consultation = PatientConsultation.objects.get(external_id=consultation_ext_id) except PatientConsultation.DoesNotExist as e: - raise CeleryTaskException( - f"Consultation {consultation_ext_id} does not exist" + raise CeleryTaskError( + f"Consultation {consultation_ext_id} does not exist", ) from e summary_file = generate_and_upload_discharge_summary(consultation) if not summary_file: - raise CeleryTaskException("Unable to generate discharge summary") + raise CeleryTaskError("Unable to generate discharge summary") return summary_file.id @@ -45,11 +47,11 @@ def generate_discharge_summary_task(consultation_ext_id: str): expires=10 * 60, ) def email_discharge_summary_task(file_id: int, emails: Iterable[str]): - logger.info(f"Emailing Discharge Summary {file_id} to {emails}") + logger.info("Emailing Discharge Summary %s to %s", file_id, emails) try: summary = FileUpload.objects.get(id=file_id) except FileUpload.DoesNotExist: - logger.error(f"Summary {file_id} does not exist") + logger.error("Summary %s does not exist", file_id) return False email_discharge_summary(summary, emails) return True diff --git a/care/facility/tasks/summarisation.py b/care/facility/tasks/summarisation.py index 774829df7c..e6b88adbd9 100644 --- a/care/facility/tasks/summarisation.py +++ b/care/facility/tasks/summarisation.py @@ -1,4 +1,7 @@ +from logging import Logger + from celery import shared_task +from celery.utils.log import get_task_logger from care.facility.utils.summarisation.district.patient_summary import ( district_patient_summary, @@ -10,32 +13,34 @@ from care.facility.utils.summarisation.tests_summary import tests_summary from care.facility.utils.summarisation.triage_summary import triage_summary +logger: Logger = get_task_logger(__name__) + @shared_task def summarise_triage(): triage_summary() - print("Summarised Triages") + logger.info("Summarised Triages") @shared_task def summarise_tests(): tests_summary() - print("Summarised Tests") + logger.info("Summarised Tests") @shared_task def summarise_facility_capacity(): facility_capacity_summary() - print("Summarised Facility Capacities") + logger.info("Summarised Facility Capacities") @shared_task def summarise_patient(): patient_summary() - print("Summarised Patients") + logger.info("Summarised Patients") @shared_task def summarise_district_patient(): district_patient_summary() - print("Summarised District Patients") + logger.info("Summarised District Patients") diff --git a/care/facility/templatetags/__init__.py b/care/facility/templatetags/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/care/facility/templatetags/filters.py b/care/facility/templatetags/filters.py index 9a8bc576fa..9b7f6cf824 100644 --- a/care/facility/templatetags/filters.py +++ b/care/facility/templatetags/filters.py @@ -1,6 +1,7 @@ from datetime import datetime from django.template import Library +from django.utils.timezone import get_default_timezone register = Library() @@ -28,6 +29,8 @@ def field_name_to_label(value): @register.filter(expects_localtime=True) def parse_datetime(value): try: - return datetime.strptime(value, "%Y-%m-%dT%H:%M") + return datetime.strptime(value, "%Y-%m-%dT%H:%M").astimezone( + get_default_timezone(), + ) except ValueError: return None diff --git a/care/facility/tests/test_asset_api.py b/care/facility/tests/test_asset_api.py index 58e7612f26..5f14361395 100644 --- a/care/facility/tests/test_asset_api.py +++ b/care/facility/tests/test_asset_api.py @@ -17,7 +17,9 @@ def setUpTestData(cls) -> None: cls.asset_location = cls.create_asset_location(cls.facility) cls.user = cls.create_user("staff", cls.district, home_facility=cls.facility) cls.patient = cls.create_patient( - cls.district, cls.facility, local_body=cls.local_body + cls.district, + cls.facility, + local_body=cls.local_body, ) def setUp(self) -> None: @@ -61,7 +63,8 @@ def test_update_asset(self): "location": self.asset_location.external_id, } response = self.client.patch( - f"/api/v1/asset/{self.asset.external_id}/", sample_data + f"/api/v1/asset/{self.asset.external_id}/", + sample_data, ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["name"], sample_data["name"]) @@ -73,7 +76,8 @@ def test_update_asset_change_warranty_improperly(self): "warranty_amc_end_of_validity": "2002-04-01", } response = self.client.patch( - f"/api/v1/asset/{self.asset.external_id}/", sample_data + f"/api/v1/asset/{self.asset.external_id}/", + sample_data, ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -82,7 +86,8 @@ def test_update_asset_change_warranty_properly(self): "warranty_amc_end_of_validity": "2222-04-01", } response = self.client.patch( - f"/api/v1/asset/{self.asset.external_id}/", sample_data + f"/api/v1/asset/{self.asset.external_id}/", + sample_data, ) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -93,7 +98,10 @@ def test_delete_asset_failure(self): def test_delete_asset(self): user = self.create_user( - "distadmin", self.district, home_facility=self.facility, user_type=30 + "distadmin", + self.district, + home_facility=self.facility, + user_type=30, ) self.client.force_authenticate(user=user) response = self.client.delete( @@ -104,15 +112,19 @@ def test_delete_asset(self): def test_asset_filter_in_use_by_consultation(self): asset1 = Asset.objects.create( - name="asset1", current_location=self.asset_location + name="asset1", + current_location=self.asset1_location, ) asset2 = Asset.objects.create( - name="asset2", current_location=self.asset_location + name="asset2", + current_location=self.asset1_location, ) consultation = self.create_consultation(self.patient, self.facility) bed = Bed.objects.create( - name="bed1", location=self.asset_location, facility=self.facility + name="bed1", + location=self.asset1_location, + facility=self.facility, ) self.client.post( "/api/v1/consultationbed/", diff --git a/care/facility/tests/test_asset_availability_api.py b/care/facility/tests/test_asset_availability_api.py index 45ea2950b0..058dedfa6c 100644 --- a/care/facility/tests/test_asset_availability_api.py +++ b/care/facility/tests/test_asset_availability_api.py @@ -28,12 +28,13 @@ def test_list_asset_availability(self): response = self.client.get("/api/v1/asset_availability/") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual( - response.data["results"][0]["status"], AvailabilityStatus.OPERATIONAL.value + response.data["results"][0]["status"], + AvailabilityStatus.OPERATIONAL.value, ) def test_retrieve_asset_availability(self): response = self.client.get( - f"/api/v1/asset_availability/{self.asset_availability.id}/" + f"/api/v1/asset_availability/{self.asset_availability.id}/", ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["status"], AvailabilityStatus.OPERATIONAL.value) diff --git a/care/facility/tests/test_asset_location_api.py b/care/facility/tests/test_asset_location_api.py index 0228a5dcb9..99be43c8b5 100644 --- a/care/facility/tests/test_asset_location_api.py +++ b/care/facility/tests/test_asset_location_api.py @@ -17,14 +17,14 @@ def setUpTestData(cls) -> None: def test_list_asset_locations(self): response = self.client.get( - f"/api/v1/facility/{self.facility.external_id}/asset_location/" + f"/api/v1/facility/{self.facility.external_id}/asset_location/", ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertContains(response, self.asset_location.external_id) def test_retrieve_asset_location(self): response = self.client.get( - f"/api/v1/facility/{self.facility.external_id}/asset_location/{self.asset_location.external_id}/" + f"/api/v1/facility/{self.facility.external_id}/asset_location/{self.asset_location.external_id}/", ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertContains(response, self.asset_location.external_id) diff --git a/care/facility/tests/test_asset_service_history_api.py b/care/facility/tests/test_asset_service_history_api.py index afa27962d1..8115d14e59 100644 --- a/care/facility/tests/test_asset_service_history_api.py +++ b/care/facility/tests/test_asset_service_history_api.py @@ -1,6 +1,6 @@ -from datetime import datetime, timedelta +from datetime import timedelta -from django.utils.timezone import now +from django.utils import timezone from rest_framework import status from rest_framework.test import APITestCase @@ -20,8 +20,8 @@ def setUpTestData(cls): cls.asset_location = cls.create_asset_location(cls.facility) cls.asset = cls.create_asset(cls.asset_location) cls.user = cls.create_user("staff", cls.district, home_facility=cls.facility) - cls.today = datetime.today().strftime("%Y-%m-%d") - cls.yesterday = (datetime.today() - timedelta(days=1)).strftime("%Y-%m-%d") + cls.today = timezone.now().strftime("%Y-%m-%d") + cls.yesterday = (timezone.now() - timedelta(days=1)).strftime("%Y-%m-%d") cls.asset_service = AssetService.objects.create( asset=cls.asset, serviced_on=cls.today, @@ -31,19 +31,19 @@ def setUpTestData(cls): asset_service=cls.asset_service, serviced_on=cls.today, note="Test Note", - edited_on=now(), + edited_on=timezone.now(), edited_by=cls.user, ) def test_list_asset_service(self): response = self.client.get( - f"/api/v1/asset/{self.asset.external_id}/service_records/" + f"/api/v1/asset/{self.asset.external_id}/service_records/", ) self.assertEqual(response.status_code, status.HTTP_200_OK) def test_retrieve_asset_service(self): response = self.client.get( - f"/api/v1/asset/{self.asset.external_id}/service_records/{self.asset_service.external_id}/" + f"/api/v1/asset/{self.asset.external_id}/service_records/{self.asset_service.external_id}/", ) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -53,7 +53,8 @@ def test_add_asset_service_record(self): sample_data = {"last_serviced_on": self.today, "note": "Hello"} response = self.client.patch( - f"/api/v1/asset/{self.asset.external_id}/", sample_data + f"/api/v1/asset/{self.asset.external_id}/", + sample_data, ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["last_service"]["serviced_on"], self.today) @@ -62,7 +63,8 @@ def test_add_asset_service_record(self): def test_update_asset_service_record(self): sample_data = {"last_serviced_on": self.today, "note": "Hello 2"} response = self.client.patch( - f"/api/v1/asset/{self.asset.external_id}/", sample_data + f"/api/v1/asset/{self.asset.external_id}/", + sample_data, ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["last_service"]["serviced_on"], self.today) diff --git a/care/facility/tests/test_asset_transaction_api.py b/care/facility/tests/test_asset_transaction_api.py index bc961504ab..150a20745e 100644 --- a/care/facility/tests/test_asset_transaction_api.py +++ b/care/facility/tests/test_asset_transaction_api.py @@ -16,13 +16,17 @@ def setUpTestData(cls): cls.user = cls.create_user("staff", cls.district, home_facility=cls.facility) cls.asset_from_location = cls.create_asset_location( - cls.facility, name="asset from location" + cls.facility, + name="asset from location", ) cls.asset_to_location = cls.create_asset_location( - cls.facility, name="asset to location" + cls.facility, + name="asset to location", ) cls.asset = cls.create_asset( - cls.asset_from_location, name="Test Asset", asset_type=50 + cls.asset_from_location, + name="Test Asset", + asset_type=50, ) cls.asset_transaction = AssetTransaction.objects.create( asset=cls.asset, @@ -37,6 +41,6 @@ def test_list_asset_transactions(self): def test_retrieve_asset_transaction(self): response = self.client.get( - f"/api/v1/asset_transaction/{self.asset_transaction.id}/" + f"/api/v1/asset_transaction/{self.asset_transaction.id}/", ) self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/care/facility/tests/test_bed_create.py b/care/facility/tests/test_bed_create.py index 9e5b27b8d5..6a917915b1 100644 --- a/care/facility/tests/test_bed_create.py +++ b/care/facility/tests/test_bed_create.py @@ -45,7 +45,10 @@ def setUpTestData(cls) -> None: cls.asset_location = cls.create_asset_location(cls.facility) cls.asset = cls.create_asset(cls.asset_location) cls.user = cls.create_user( - "distadmin", cls.district, home_facility=cls.facility, user_type=30 + "distadmin", + cls.district, + home_facility=cls.facility, + user_type=30, ) def test_create(self): diff --git a/care/facility/tests/test_consultation_bed_asset_api.py b/care/facility/tests/test_consultation_bed_asset_api.py index 7539ff0f16..1684403641 100644 --- a/care/facility/tests/test_consultation_bed_asset_api.py +++ b/care/facility/tests/test_consultation_bed_asset_api.py @@ -1,5 +1,4 @@ -from datetime import datetime - +from django.utils import timezone from rest_framework import status from rest_framework.test import APITestCase @@ -19,25 +18,34 @@ def setUpTestData(cls) -> None: cls.asset = cls.create_asset(cls.asset_location) cls.user = cls.create_user("staff", cls.district, home_facility=cls.facility) cls.patient = cls.create_patient( - cls.district, cls.facility, local_body=cls.local_body + cls.district, + cls.facility, + local_body=cls.local_body, ) def setUp(self) -> None: super().setUp() self.bed1 = Bed.objects.create( - name="bed1", location=self.asset_location, facility=self.facility + name="bed1", + location=self.asset_location, + facility=self.facility, ) self.bed2 = Bed.objects.create( - name="bed2", location=self.asset_location, facility=self.facility + name="bed2", + location=self.asset_location, + facility=self.facility, ) self.asset1 = Asset.objects.create( - name="asset1", current_location=self.asset_location + name="asset1", + current_location=self.asset_location, ) self.asset2 = Asset.objects.create( - name="asset2", current_location=self.asset_location + name="asset2", + current_location=self.asset_location, ) self.asset3 = Asset.objects.create( - name="asset3", current_location=self.asset_location + name="asset3", + current_location=self.asset_location, ) def test_link_asset_to_consultation_bed(self): @@ -47,7 +55,7 @@ def test_link_asset_to_consultation_bed(self): { "consultation": consultation.external_id, "bed": self.bed1.external_id, - "start_date": datetime.now().isoformat(), + "start_date": timezone.now().isoformat(), "assets": [self.asset1.external_id, self.asset2.external_id], }, ) @@ -61,7 +69,7 @@ def test_link_asset_to_consultation_bed_with_asset_already_in_use(self): { "consultation": consultation.external_id, "bed": self.bed1.external_id, - "start_date": datetime.now().isoformat(), + "start_date": timezone.now().isoformat(), "assets": [self.asset1.external_id, self.asset2.external_id], }, ) @@ -71,7 +79,7 @@ def test_link_asset_to_consultation_bed_with_asset_already_in_use(self): { "consultation": consultation2.external_id, "bed": self.bed2.external_id, - "start_date": datetime.now().isoformat(), + "start_date": timezone.now().isoformat(), "assets": [self.asset1.external_id, self.asset3.external_id], }, ) diff --git a/care/facility/tests/test_facilityuser_api.py b/care/facility/tests/test_facilityuser_api.py index 0f57868a08..23bcd3c512 100644 --- a/care/facility/tests/test_facilityuser_api.py +++ b/care/facility/tests/test_facilityuser_api.py @@ -20,7 +20,7 @@ def setUpTestData(cls) -> None: def test_get_queryset_with_prefetching(self): response = self.client.get( - f"/api/v1/facility/{self.facility.external_id}/get_users/" + f"/api/v1/facility/{self.facility.external_id}/get_users/", ) self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/care/facility/tests/test_medicine_administrations_api.py b/care/facility/tests/test_medicine_administrations_api.py index 6283543e7a..d6f89dbf90 100644 --- a/care/facility/tests/test_medicine_administrations_api.py +++ b/care/facility/tests/test_medicine_administrations_api.py @@ -16,7 +16,7 @@ def setUpTestData(cls) -> None: cls.facility = cls.create_facility(cls.super_user, cls.district, cls.local_body) cls.user = cls.create_user("staff1", cls.district, home_facility=cls.facility) cls.patient = cls.create_patient( - cls.district, cls.facility, local_body=cls.local_body + cls.district, cls.facility, local_body=cls.local_body, ) def setUp(self) -> None: @@ -33,7 +33,7 @@ def create_prescription(self, **kwargs): "is_prn": False, } return Prescription.objects.create( - **{**data, **kwargs, "prescribed_by": self.user} + **{**data, **kwargs, "prescribed_by": self.user}, ) def test_administer(self): @@ -62,7 +62,7 @@ def test_administer_in_past(self): def test_administer_discontinued(self): prescription = self.create_prescription( - discontinued=True, discontinued_date=timezone.now() + discontinued=True, discontinued_date=timezone.now(), ) res = self.client.post( f"/api/v1/consultation/{prescription.consultation.external_id}/prescriptions/{prescription.external_id}/administer/", diff --git a/care/facility/tests/test_patient.py b/care/facility/tests/test_patient.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/care/facility/tests/test_patient_api.py b/care/facility/tests/test_patient_api.py index b4885b9fe8..c09dbe7b43 100644 --- a/care/facility/tests/test_patient_api.py +++ b/care/facility/tests/test_patient_api.py @@ -82,33 +82,52 @@ def setUpTestData(cls) -> None: cls.super_user = cls.create_super_user("su", cls.district) cls.facility = cls.create_facility(cls.super_user, cls.district, cls.local_body) cls.user = cls.create_user( - "doctor1", cls.district, home_facility=cls.facility, user_type=15 + "doctor1", + cls.district, + home_facility=cls.facility, + user_type=15, ) cls.asset_location = cls.create_asset_location(cls.facility) cls.asset = cls.create_asset(cls.asset_location) cls.state_admin = cls.create_user( - "state-admin", cls.district, home_facility=cls.facility, user_type=40 + "state-admin", + cls.district, + home_facility=cls.facility, + user_type=40, ) cls.facility2 = cls.create_facility( - cls.super_user, cls.district, cls.local_body + cls.super_user, + cls.district, + cls.local_body, ) cls.user2 = cls.create_user( - "doctor2", cls.district, home_facility=cls.facility2, user_type=15 + "doctor2", + cls.district, + home_facility=cls.facility2, + user_type=15, ) cls.patient = cls.create_patient(cls.district, cls.facility) def setUp(self): super().setUp() self.create_patient_note( - patient=self.patient, facility=self.facility, created_by=self.user + patient=self.patient, + facility=self.facility, + created_by=self.user, ) self.create_patient_note( - patient=self.patient, facility=self.facility, created_by=self.user2 + patient=self.patient, + facility=self.facility, + created_by=self.user2, ) def create_patient_note( - self, patient=None, note="Patient is doing find", created_by=None, **kwargs + self, + patient=None, + note="Patient is doing find", + created_by=None, + **kwargs, ): data = { "facility": patient.facility or self.facility, @@ -119,8 +138,8 @@ def create_patient_note( self.client.post(f"/api/v1/patient/{patient.external_id}/notes/", data=data) def test_patient_notes(self): - patientId = self.patient.external_id - response = self.client.get(f"/api/v1/patient/{patientId}/notes/") + patient_id = self.patient.external_id + response = self.client.get(f"/api/v1/patient/{patient_id}/notes/") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIsInstance(response.json()["results"], list) @@ -134,7 +153,8 @@ def test_patient_notes(self): data = response.json()["results"][1] self.assertCountEqual( - data.keys(), [item.value for item in ExpectedPatientNoteKeys] + data.keys(), + [item.value for item in ExpectedPatientNoteKeys], ) user_type_content = data["user_type"] @@ -145,7 +165,8 @@ def test_patient_notes(self): if facility_content is not None: self.assertCountEqual( - facility_content.keys(), [item.value for item in ExpectedFacilityKeys] + facility_content.keys(), + [item.value for item in ExpectedFacilityKeys], ) ward_object_content = facility_content["ward_object"] @@ -206,7 +227,10 @@ def setUpTestData(cls): cls.super_user = cls.create_super_user("su", cls.district) cls.facility = cls.create_facility(cls.super_user, cls.district, cls.local_body) cls.user = cls.create_user( - "doctor1", cls.district, home_facility=cls.facility, user_type=15 + "doctor1", + cls.district, + home_facility=cls.facility, + user_type=15, ) cls.patient = cls.create_patient(cls.district, cls.facility) cls.consultation = cls.create_consultation( @@ -226,5 +250,6 @@ def test_filter_by_patient_no(self): self.assertEqual(response.status_code, 200) self.assertEqual(response.data["count"], 1) self.assertEqual( - response.data["results"][0]["id"], str(self.patient.external_id) + response.data["results"][0]["id"], + str(self.patient.external_id), ) diff --git a/care/facility/tests/test_patient_consultation_api.py b/care/facility/tests/test_patient_consultation_api.py index 4330ebb170..c1bc512b86 100644 --- a/care/facility/tests/test_patient_consultation_api.py +++ b/care/facility/tests/test_patient_consultation_api.py @@ -21,7 +21,7 @@ def setUpTestData(cls) -> None: cls.facility = cls.create_facility(cls.super_user, cls.district, cls.local_body) cls.user = cls.create_user("staff1", cls.district, home_facility=cls.facility) cls.doctor = cls.create_user( - "doctor", cls.district, home_facility=cls.facility, user_type=15 + "doctor", cls.district, home_facility=cls.facility, user_type=15, ) def get_default_data(self): @@ -47,7 +47,7 @@ def create_admission_consultation(self, patient=None, **kwargs): { "patient": patient.external_id, "facility": self.facility.external_id, - } + }, ) data.update(kwargs) res = self.client.post(self.get_url(), data) @@ -58,7 +58,7 @@ def update_consultation(self, consultation, **kwargs): def discharge(self, consultation, **kwargs): return self.client.post( - f"{self.get_url(consultation)}discharge_patient/", kwargs, "json" + f"{self.get_url(consultation)}discharge_patient/", kwargs, "json", ) def test_create_consultation_verified_by_invalid_user(self): @@ -67,7 +67,7 @@ def test_create_consultation_verified_by_invalid_user(self): admission_date=make_aware(datetime.datetime(2020, 4, 1, 15, 30, 00)), ) res = self.update_consultation( - consultation, verified_by=self.doctor.id, suggestion="A" + consultation, verified_by=self.doctor.id, suggestion="A", ) self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) diff --git a/care/facility/tests/test_patientfilterset.py b/care/facility/tests/test_patientfilterset.py index c06314b397..ebc6cd5c09 100644 --- a/care/facility/tests/test_patientfilterset.py +++ b/care/facility/tests/test_patientfilterset.py @@ -41,21 +41,21 @@ def test_filter_by_bed_type(self): bed2 = Bed.objects.create(**bed2_data) consultation1 = self.create_consultation( - patient=patient1, facility=self.facility + patient=patient1, facility=self.facility, ) consultation2 = self.create_consultation( - patient=patient2, facility=self.facility + patient=patient2, facility=self.facility, ) consultation3 = self.create_consultation( - patient=patient3, facility=self.facility + patient=patient3, facility=self.facility, ) # consultation beds consultation_bed1 = ConsultationBed.objects.create( - consultation=consultation1, bed=bed1, start_date=timezone.now() + consultation=consultation1, bed=bed1, start_date=timezone.now(), ) consultation_bed2 = ConsultationBed.objects.create( - consultation=consultation2, bed=bed2, start_date=timezone.now() + consultation=consultation2, bed=bed2, start_date=timezone.now(), ) consultation1.current_bed = consultation_bed1 diff --git a/care/facility/utils/icd/__init__.py b/care/facility/utils/icd/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/care/facility/utils/icd/scraper.py b/care/facility/utils/icd/scraper.py index 936ac5f4c2..c8befee51c 100644 --- a/care/facility/utils/icd/scraper.py +++ b/care/facility/utils/icd/scraper.py @@ -1,9 +1,15 @@ import json +import logging import time import requests from django.conf import settings +# ruff: noqa: G004 +logger = logging.getLogger(__name__) + +ICD_REQUEST_TIMEOUT = 30 + class ICDScraper: def __init__(self): @@ -12,22 +18,15 @@ def __init__(self): self.scraped_concepts = [] self.scraped_concept_dict = {} - def add_query(self, url, query={}): - return ( - url - + "?" - + "&".join(map(lambda k: str(k) + "=" + str(query[k]), query.keys())) - ) - def get_child_concepts(self, p_concept, p_parent_id): if p_concept["ID"] in self.scraped_concept_dict: - print(f"[-] Skipped duplicate, {p_concept['label']}") + logger.info(f"[-] Skipped duplicate, {p_concept['label']}") return self.scraped_concepts.append({**p_concept, "parentId": p_parent_id}) self.scraped_concept_dict[p_concept["ID"]] = True - print(f"[+] Added {p_concept['label']}") + logger.info(f"[+] Added {p_concept['label']}") if p_concept["isLeaf"]: return @@ -35,28 +34,26 @@ def get_child_concepts(self, p_concept, p_parent_id): concepts = [] try: concepts = requests.get( - self.add_query( - self.child_concept_url, - { - "useHtml": "false", - "ConceptId": p_concept["ID"], - }, - ) + self.child_concept_url, + params={ + "useHtml": "false", + "ConceptId": p_concept["ID"], + }, + timeout=ICD_REQUEST_TIMEOUT, ).json() except Exception as e: - print("[x] Error encountered: ", e) + logger.error("[x] Error encountered", exc_info=e) with open("error.txt", "a") as error_file: error_file.write(f"{p_concept['label']}\n") time.sleep(10) concepts = requests.get( - self.add_query( - self.child_concept_url, - { - "useHtml": "false", - "ConceptId": p_concept["ID"], - }, - ) + self.child_concept_url, + params={ + "useHtml": "false", + "ConceptId": p_concept["ID"], + }, + timeout=ICD_REQUEST_TIMEOUT, ).json() for concept in concepts: @@ -66,7 +63,9 @@ def scrape(self): self.scraped_concepts = [] self.scraped_concept_dict = {} root_concepts = requests.get( - self.add_query(self.root_concept_url, {"useHtml": "false"}) + self.root_concept_url, + params={"useHtml": "false"}, + timeout=ICD_REQUEST_TIMEOUT, ).json() skip = [ diff --git a/care/facility/utils/reports/__init__.py b/care/facility/utils/reports/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/care/facility/utils/reports/discharge_summary.py b/care/facility/utils/reports/discharge_summary.py index 072ca43266..94c3f5e356 100644 --- a/care/facility/utils/reports/discharge_summary.py +++ b/care/facility/utils/reports/discharge_summary.py @@ -46,24 +46,25 @@ def clear_lock(consultation_ext_id: str): def get_discharge_summary_data(consultation: PatientConsultation): - logger.info(f"fetching discharge summary data for {consultation.external_id}") + logger.info("fetching discharge summary data for %s", consultation.external_id) samples = PatientSample.objects.filter( - patient=consultation.patient, consultation=consultation + patient=consultation.patient, + consultation=consultation, ) hcx = Policy.objects.filter(patient=consultation.patient) daily_rounds = DailyRound.objects.filter(consultation=consultation) diagnosis = get_icd11_diagnoses_objects_by_ids(consultation.icd11_diagnoses) provisional_diagnosis = get_icd11_diagnoses_objects_by_ids( - consultation.icd11_provisional_diagnoses + consultation.icd11_provisional_diagnoses, ) principal_diagnosis = get_icd11_diagnoses_objects_by_ids( [consultation.icd11_principal_diagnosis] if consultation.icd11_principal_diagnosis - else [] + else [], ) investigations = InvestigationValue.objects.filter( Q(consultation=consultation.id) - & (Q(value__isnull=False) | Q(notes__isnull=False)) + & (Q(value__isnull=False) | Q(notes__isnull=False)), ) medical_history = Disease.objects.filter(patient=consultation.patient) prescriptions = Prescription.objects.filter( @@ -114,12 +115,14 @@ def get_discharge_summary_data(consultation: PatientConsultation): def generate_discharge_summary_pdf(data, file): logger.info( - f"Generating Discharge Summary html for {data['consultation'].external_id}" + "Generating Discharge Summary html for %s", + data["consultation"].external_id, ) html_string = render_to_string("reports/patient_discharge_summary_pdf.html", data) logger.info( - f"Generating Discharge Summary pdf for {data['consultation'].external_id}" + "Generating Discharge Summary pdf for %s", + data["consultation"].external_id, ) bytestring_to_pdf( html_string.encode(), @@ -134,7 +137,7 @@ def generate_discharge_summary_pdf(data, file): def generate_and_upload_discharge_summary(consultation: PatientConsultation): - logger.info(f"Generating Discharge Summary for {consultation.external_id}") + logger.info("Generating Discharge Summary for %s", consultation.external_id) set_lock(consultation.external_id, 5) try: @@ -153,12 +156,14 @@ def generate_and_upload_discharge_summary(consultation: PatientConsultation): set_lock(consultation.external_id, 50) with tempfile.NamedTemporaryFile(suffix=".pdf") as file: generate_discharge_summary_pdf(data, file) - logger.info(f"Uploading Discharge Summary for {consultation.external_id}") + logger.info("Uploading Discharge Summary for %s", consultation.external_id) summary_file.put_object(file, ContentType="application/pdf") summary_file.upload_completed = True summary_file.save() logger.info( - f"Uploaded Discharge Summary for {consultation.external_id}, file id: {summary_file.id}" + "Uploaded Discharge Summary for %s, file id: %s", + consultation.external_id, + summary_file.id, ) finally: clear_lock(consultation.external_id) diff --git a/care/facility/utils/summarisation/__init__.py b/care/facility/utils/summarisation/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/care/facility/utils/summarisation/district/__init__.py b/care/facility/utils/summarisation/district/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/care/facility/utils/summarisation/district/patient_summary.py b/care/facility/utils/summarisation/district/patient_summary.py index 31997109fb..a56acf9b6b 100644 --- a/care/facility/utils/summarisation/district/patient_summary.py +++ b/care/facility/utils/summarisation/district/patient_summary.py @@ -13,7 +13,7 @@ def district_patient_summary(): "id": district_object.id, } for local_body_object in LocalBody.objects.filter( - district_id=district_object.id + district_id=district_object.id, ): district_summary[local_body_object.id] = { "name": local_body_object.name, @@ -34,7 +34,7 @@ def district_patient_summary(): for bed_type_choice in BedTypeChoices: db_value, text = bed_type_choice patient_filters = { - "last_consultation__" + "current_bed__bed__bed_type": db_value + "last_consultation__" + "current_bed__bed__bed_type": db_value, } count = patients.filter(**patient_filters).count() clean_name = "total_patients_" + "_".join(text.lower().split()) @@ -50,19 +50,19 @@ def district_patient_summary(): # Apply Date Filters patients_today = patients.filter( - last_consultation__created_date__startswith=now().date() + last_consultation__created_date__startswith=now().date(), ) # Get Todays Counts today_patients_home_quarantine = patients_today.filter( - home_quarantine + home_quarantine, ).count() for bed_type_choice in BedTypeChoices: db_value, text = bed_type_choice patient_filters = { - "last_consultation__" + "current_bed__bed__bed_type": db_value + "last_consultation__" + "current_bed__bed__bed_type": db_value, } count = patients_today.filter(**patient_filters).count() clean_name = "today_patients_" + "_".join(text.lower().split()) @@ -74,7 +74,7 @@ def district_patient_summary(): ] = today_patients_home_quarantine object_filter = Q(s_type="PatientSummary") & Q( - created_date__startswith=now().date() + created_date__startswith=now().date(), ) if ( DistrictScopedSummary.objects.filter(district_id=district_object.id) @@ -82,7 +82,7 @@ def district_patient_summary(): .exists() ): district_summary_old = DistrictScopedSummary.objects.filter( - object_filter + object_filter, ).get(district_id=district_object.id) district_summary_old.created_date = now() district_summary_old.data.pop("modified_date") @@ -90,7 +90,7 @@ def district_patient_summary(): district_summary_old.data = district_summary latest_modification_date = now() district_summary_old.data.update( - {"modified_date": latest_modification_date.strftime("%d-%m-%Y %H:%M")} + {"modified_date": latest_modification_date.strftime("%d-%m-%Y %H:%M")}, ) district_summary_old.save() else: diff --git a/care/facility/utils/summarisation/facility_capacity.py b/care/facility/utils/summarisation/facility_capacity.py index 63548b9c3c..3069c54076 100644 --- a/care/facility/utils/summarisation/facility_capacity.py +++ b/care/facility/utils/summarisation/facility_capacity.py @@ -24,11 +24,11 @@ def facility_capacity_summary(): for facility_obj in Facility.objects.all(): # Calculate Actual Patients Discharged and Live in this Facility patients_in_facility = PatientRegistration.objects.filter( - facility_id=facility_obj.id + facility_id=facility_obj.id, ).select_related("state", "district", "local_body") capacity_summary[facility_obj.id] = FacilitySerializer(facility_obj).data capacity_summary[facility_obj.id]["features"] = list( - capacity_summary[facility_obj.id]["features"] + capacity_summary[facility_obj.id]["features"], ) capacity_summary[facility_obj.id][ "actual_live_patients" @@ -41,11 +41,12 @@ def facility_capacity_summary(): temp_inventory_summary_obj = {} summary_objs = FacilityInventorySummary.objects.filter( - facility_id=facility_obj.id + facility_id=facility_obj.id, ) for summary_obj in summary_objs: burn_rate = FacilityInventoryBurnRate.objects.filter( - facility_id=facility_obj.id, item_id=summary_obj.item.id + facility_id=facility_obj.id, + item_id=summary_obj.item.id, ).first() log_query = FacilityInventoryLog.objects.filter( facility_id=facility_obj.id, @@ -66,13 +67,13 @@ def facility_capacity_summary(): end_stock = end_log.current_stock total_consumed = 0 temp1 = log_query.filter(is_incoming=False).aggregate( - Sum("quantity_in_default_unit") + Sum("quantity_in_default_unit"), ) if temp1: total_consumed = temp1.get("quantity_in_default_unit__sum", 0) or 0 total_added = 0 temp2 = log_query.filter(is_incoming=True).aggregate( - Sum("quantity_in_default_unit") + Sum("quantity_in_default_unit"), ) if temp2: total_added = temp2.get("quantity_in_default_unit__sum", 0) or 0 @@ -108,7 +109,7 @@ def facility_capacity_summary(): if "availability" not in capacity_summary[facility_id]: capacity_summary[facility_id]["availability"] = [] capacity_summary[facility_id]["availability"].append( - FacilityCapacitySerializer(capacity_object).data + FacilityCapacitySerializer(capacity_object).data, ) for i in capacity_summary: @@ -125,7 +126,8 @@ def facility_capacity_summary(): ) else: facility_summary_obj = FacilityRelatedSummary( - s_type="FacilityCapacity", facility_id=i + s_type="FacilityCapacity", + facility_id=i, ) facility_summary_obj.data = capacity_summary[i] facility_summary_obj.save() diff --git a/care/facility/utils/summarisation/patient_summary.py b/care/facility/utils/summarisation/patient_summary.py index 3f3ca1a068..74abcf4620 100644 --- a/care/facility/utils/summarisation/patient_summary.py +++ b/care/facility/utils/summarisation/patient_summary.py @@ -28,7 +28,7 @@ def patient_summary(): for bed_type_choice in BedTypeChoices: db_value, text = bed_type_choice patient_filters = { - "last_consultation__" + "current_bed__bed__bed_type": db_value + "last_consultation__" + "current_bed__bed__bed_type": db_value, } count = patients.filter(**patient_filters).count() clean_name = "total_patients_" + "_".join(text.lower().split()) @@ -44,19 +44,19 @@ def patient_summary(): # Apply Date Filters patients_today = patients.filter( - last_consultation__created_date__startswith=now().date() + last_consultation__created_date__startswith=now().date(), ) # Get Todays Counts today_patients_home_quarantine = patients_today.filter( - home_quarantine + home_quarantine, ).count() for bed_type_choice in BedTypeChoices: db_value, text = bed_type_choice patient_filters = { - "last_consultation__" + "current_bed__bed__bed_type": db_value + "last_consultation__" + "current_bed__bed__bed_type": db_value, } count = patients_today.filter(**patient_filters).count() clean_name = "today_patients_" + "_".join(text.lower().split()) @@ -69,7 +69,7 @@ def patient_summary(): for i in list(patient_summary.keys()): object_filter = Q(s_type="PatientSummary") & Q( - created_date__startswith=now().date() + created_date__startswith=now().date(), ) if ( FacilityRelatedSummary.objects.filter(facility_id=i) @@ -77,7 +77,7 @@ def patient_summary(): .exists() ): facility = FacilityRelatedSummary.objects.filter(object_filter).get( - facility_id=i + facility_id=i, ) facility.created_date = now() facility.data.pop("modified_date") @@ -87,17 +87,19 @@ def patient_summary(): facility.data.update( { "modified_date": latest_modification_date.strftime( - "%d-%m-%Y %H:%M" - ) - } + "%d-%m-%Y %H:%M", + ), + }, ) facility.save() else: modified_date = now() patient_summary[i].update( - {"modified_date": modified_date.strftime("%d-%m-%Y %H:%M")} + {"modified_date": modified_date.strftime("%d-%m-%Y %H:%M")}, ) FacilityRelatedSummary( - s_type="PatientSummary", facility_id=i, data=patient_summary[i] + s_type="PatientSummary", + facility_id=i, + data=patient_summary[i], ).save() return True diff --git a/care/facility/utils/summarisation/tests_summary.py b/care/facility/utils/summarisation/tests_summary.py index 4854a8fa5d..ce30abbaac 100644 --- a/care/facility/utils/summarisation/tests_summary.py +++ b/care/facility/utils/summarisation/tests_summary.py @@ -11,20 +11,20 @@ def tests_summary(): facility.consultations.all().distinct("patient_id").count() ) facility_patients_samples = PatientSample.objects.filter( - consultation__facility_id=facility.id + consultation__facility_id=facility.id, ) total_tests_count = facility_patients_samples.count() results_positive_count = facility_patients_samples.filter( - result=PatientSample.SAMPLE_TEST_RESULT_MAP["POSITIVE"] + result=PatientSample.SAMPLE_TEST_RESULT_MAP["POSITIVE"], ).count() results_awaited_count = facility_patients_samples.filter( - result=PatientSample.SAMPLE_TEST_RESULT_MAP["AWAITING"] + result=PatientSample.SAMPLE_TEST_RESULT_MAP["AWAITING"], ).count() results_negative_count = facility_patients_samples.filter( - result=PatientSample.SAMPLE_TEST_RESULT_MAP["NEGATIVE"] + result=PatientSample.SAMPLE_TEST_RESULT_MAP["NEGATIVE"], ).count() test_discarded_count = facility_patients_samples.filter( - result=PatientSample.SAMPLE_TEST_RESULT_MAP["INVALID"] + result=PatientSample.SAMPLE_TEST_RESULT_MAP["INVALID"], ).count() facility_tests_summarised_data = { "facility_name": facility.name, @@ -55,7 +55,7 @@ def tests_summary(): except ObjectDoesNotExist: modified_date = timezone.now() facility_tests_summarised_data["modified_date"] = modified_date.strftime( - "%d-%m-%Y %H:%M" + "%d-%m-%Y %H:%M", ) FacilityRelatedSummary.objects.create( s_type="TestSummary", diff --git a/care/facility/utils/summarisation/triage_summary.py b/care/facility/utils/summarisation/triage_summary.py index df5f4a296c..d3d026e241 100644 --- a/care/facility/utils/summarisation/triage_summary.py +++ b/care/facility/utils/summarisation/triage_summary.py @@ -13,7 +13,7 @@ def triage_summary(): current_date = localtime(now()).replace(hour=0, minute=0, second=0, microsecond=0) for facility in facilities: facility_patient_data = FacilityPatientStatsHistory.objects.filter( - facility=facility + facility=facility, ).aggregate( total_patients_visited=Sum("num_patients_visited"), total_patients_home_quarantine=Sum("num_patients_home_quarantine"), @@ -24,29 +24,34 @@ def triage_summary(): ) total_count = facility_patient_data.get("total_count", 0) total_patients_home_quarantine = facility_patient_data.get( - "total_patients_home_quarantine", 0 + "total_patients_home_quarantine", + 0, ) total_patients_referred = facility_patient_data.get( - "total_patients_referred", 0 + "total_patients_referred", + 0, ) total_patients_isolation = facility_patient_data.get( - "total_patients_visited", 0 + "total_patients_visited", + 0, ) total_patients_visited = facility_patient_data.get( - "total_patients_isolation", 0 + "total_patients_isolation", + 0, ) total_patients_confirmed_positive = facility_patient_data.get( - "num_patient_confirmed_positive", 0 + "num_patient_confirmed_positive", + 0, ) if total_count: avg_patients_home_quarantine = int( - total_patients_home_quarantine / total_count + total_patients_home_quarantine / total_count, ) avg_patients_referred = int(total_patients_referred / total_count) avg_patients_isolation = int(total_patients_isolation / total_count) avg_patients_visited = int(total_patients_visited / total_count) avg_patients_confirmed_positive = int( - total_patients_confirmed_positive / total_count + total_patients_confirmed_positive / total_count, ) else: avg_patients_home_quarantine = 0 @@ -71,7 +76,9 @@ def triage_summary(): } facility_triage_summary = FacilityRelatedSummary.objects.filter( - s_type="TriageSummary", facility=facility, created_date__gte=current_date + s_type="TriageSummary", + facility=facility, + created_date__gte=current_date, ).first() if facility_triage_summary: diff --git a/care/hcx/api/serializers/__init__.py b/care/hcx/api/serializers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/care/hcx/api/serializers/claim.py b/care/hcx/api/serializers/claim.py index c6466179d2..7f3c1509d4 100644 --- a/care/hcx/api/serializers/claim.py +++ b/care/hcx/api/serializers/claim.py @@ -38,7 +38,8 @@ class ClaimSerializer(ModelSerializer): consultation = UUIDField(write_only=True, required=True) consultation_object = PatientConsultationSerializer( - source="consultation", read_only=True + source="consultation", + read_only=True, ) policy = UUIDField(write_only=True, required=True) @@ -51,7 +52,10 @@ class ClaimSerializer(ModelSerializer): use = ChoiceField(choices=USE_CHOICES, default="claim") status = ChoiceField(choices=STATUS_CHOICES, default="active") priority = ChoiceField(choices=PRIORITY_CHOICES, default="normal") - type = ChoiceField(choices=CLAIM_TYPE_CHOICES, default="institutional") + type = ChoiceField( # noqa: A003 + choices=CLAIM_TYPE_CHOICES, + default="institutional", + ) outcome = ChoiceField(choices=OUTCOME_CHOICES, read_only=True) error_text = CharField(read_only=True) @@ -67,16 +71,16 @@ class Meta: def validate(self, attrs): if "consultation" in attrs and "policy" in attrs: consultation = get_object_or_404( - PatientConsultation.objects.filter(external_id=attrs["consultation"]) + PatientConsultation.objects.filter(external_id=attrs["consultation"]), ) policy = get_object_or_404( - Policy.objects.filter(external_id=attrs["policy"]) + Policy.objects.filter(external_id=attrs["policy"]), ) attrs["consultation"] = consultation attrs["policy"] = policy else: raise ValidationError( - {"consultation": "Field is Required", "policy": "Field is Required"} + {"consultation": "Field is Required", "policy": "Field is Required"}, ) if "total_claim_amount" not in attrs and "items" in attrs: diff --git a/care/hcx/api/serializers/communication.py b/care/hcx/api/serializers/communication.py index 969654c146..3e8f9fb357 100644 --- a/care/hcx/api/serializers/communication.py +++ b/care/hcx/api/serializers/communication.py @@ -16,7 +16,9 @@ class CommunicationSerializer(ModelSerializer): id = UUIDField(source="external_id", read_only=True) claim = ExternalIdSerializerField( - queryset=Claim.objects.all(), write_only=True, required=True + queryset=Claim.objects.all(), + write_only=True, + required=True, ) claim_object = ClaimSerializer(source="claim", read_only=True) diff --git a/care/hcx/api/serializers/gateway.py b/care/hcx/api/serializers/gateway.py index 8b36efe03b..e03f658ca9 100644 --- a/care/hcx/api/serializers/gateway.py +++ b/care/hcx/api/serializers/gateway.py @@ -34,5 +34,6 @@ def validate(self, attrs): class SendCommunicationSerializer(Serializer): communication = ExternalIdSerializerField( - queryset=Communication.objects.all(), required=True + queryset=Communication.objects.all(), + required=True, ) diff --git a/care/hcx/api/serializers/policy.py b/care/hcx/api/serializers/policy.py index 9175399edd..403b5d5fb4 100644 --- a/care/hcx/api/serializers/policy.py +++ b/care/hcx/api/serializers/policy.py @@ -50,7 +50,7 @@ class Meta: def validate(self, attrs): if "patient" in attrs: patient = get_object_or_404( - PatientRegistration.objects.filter(external_id=attrs["patient"]) + PatientRegistration.objects.filter(external_id=attrs["patient"]), ) attrs["patient"] = patient else: diff --git a/care/hcx/api/viewsets/__init__.py b/care/hcx/api/viewsets/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/care/hcx/api/viewsets/gateway.py b/care/hcx/api/viewsets/gateway.py index e584cad958..0e960e581e 100644 --- a/care/hcx/api/viewsets/gateway.py +++ b/care/hcx/api/viewsets/gateway.py @@ -4,6 +4,7 @@ from uuid import uuid4 as uuid from django.db.models import Q +from django.utils import timezone from drf_spectacular.utils import extend_schema from rest_framework import status from rest_framework.decorators import action @@ -74,7 +75,7 @@ def check_eligibility(self, request): "male" if policy["patient_object"]["gender"] == 1 else "female" - if policy["patient_object"]["gender"] == 2 + if policy["patient_object"]["gender"] == 2 # noqa: PLR2004 else "other", policy["subscriber_id"], policy["policy_id"], @@ -94,10 +95,10 @@ def check_eligibility(self, request): # {"message": "Invalid FHIR object"}, status=status.HTTP_400_BAD_REQUEST # ) - response = Hcx().generateOutgoingHcxCall( - fhirPayload=json.loads(eligibility_check_fhir_bundle.json()), + response = Hcx().generate_outgoing_hcx_call( + fhir_payload=json.loads(eligibility_check_fhir_bundle.json()), operation=HcxOperations.COVERAGE_ELIGIBILITY_CHECK, - recipientCode=policy["insurer_id"], + recipient_code=policy["insurer_id"], ) return Response(dict(response.get("response")), status=status.HTTP_200_OK) @@ -112,7 +113,7 @@ def make_claim(self, request): claim = ClaimSerializer(Claim.objects.get(external_id=data["claim"])).data consultation = PatientConsultation.objects.get( - external_id=claim["consultation_object"]["id"] + external_id=claim["consultation_object"]["id"], ) procedures = [] @@ -127,15 +128,18 @@ def make_claim(self, request): else procedure["frequency"], "status": ( "completed" - if datetime.strptime(procedure["time"], "%Y-%m-%dT%H:%M") - < datetime.now() + if datetime.strptime( + procedure["time"], + "%Y-%m-%dT%H:%M", + ).astimezone(timezone.get_current_timezone()) + < timezone.now() else "preparation" ) if "time" in procedure else "in-progress", }, consultation.procedure, - ) + ), ) diagnoses = [] @@ -152,9 +156,9 @@ def make_claim(self, request): map( lambda icd_id: ICDDiseases.by.id[icd_id], consultation.icd11_diagnoses, - ) + ), ), - ) + ), ) if len(consultation.icd11_provisional_diagnoses): @@ -170,14 +174,14 @@ def make_claim(self, request): map( lambda icd_id: ICDDiseases.by.id[icd_id], consultation.icd11_provisional_diagnoses, - ) + ), ), - ) + ), ) previous_claim = ( Claim.objects.filter( - consultation__external_id=claim["consultation_object"]["id"] + consultation__external_id=claim["consultation_object"]["id"], ) .order_by("-modified_date") .exclude(external_id=claim["id"]) @@ -186,7 +190,7 @@ def make_claim(self, request): related_claims = [] if previous_claim: related_claims.append( - {"id": str(previous_claim.external_id), "type": "prior"} + {"id": str(previous_claim.external_id), "type": "prior"}, ) docs = list( @@ -200,21 +204,21 @@ def make_claim(self, request): ), FileUpload.objects.filter( Q(associating_id=claim["consultation_object"]["id"]) - | Q(associating_id=claim["id"]) + | Q(associating_id=claim["id"]), ), - ) + ), ) if REVERSE_USE_CHOICES[claim["use"]] == "claim": discharge_summary_url = generate_discharge_report_signed_url( - claim["policy_object"]["patient_object"]["id"] + claim["policy_object"]["patient_object"]["id"], ) docs.append( { "type": "DIA", "name": "Discharge Summary", "url": discharge_summary_url, - } + }, ) claim_fhir_bundle = Fhir().create_claim_bundle( @@ -231,7 +235,7 @@ def make_claim(self, request): "male" if claim["policy_object"]["patient_object"]["gender"] == 1 else "female" - if claim["policy_object"]["patient_object"]["gender"] == 2 + if claim["policy_object"]["patient_object"]["gender"] == 2 # noqa: PLR2004 else "other", claim["policy_object"]["subscriber_id"], claim["policy_object"]["policy_id"], @@ -255,12 +259,12 @@ def make_claim(self, request): # status=status.HTTP_400_BAD_REQUEST, # ) - response = Hcx().generateOutgoingHcxCall( - fhirPayload=json.loads(claim_fhir_bundle.json()), + response = Hcx().generate_outgoing_hcx_call( + fhir_payload=json.loads(claim_fhir_bundle.json()), operation=HcxOperations.CLAIM_SUBMIT if REVERSE_USE_CHOICES[claim["use"]] == "claim" else HcxOperations.PRE_AUTH_SUBMIT, - recipientCode=claim["policy_object"]["insurer_id"], + recipient_code=claim["policy_object"]["insurer_id"], ) return Response(dict(response.get("response")), status=status.HTTP_200_OK) @@ -274,7 +278,9 @@ def send_communication(self, request): serializer.is_valid(raise_exception=True) communication = CommunicationSerializer( - get_communications(self.request.user).get(external_id=data["communication"]) + get_communications(self.request.user).get( + external_id=data["communication"], + ), ).data payload = [ @@ -289,7 +295,7 @@ def send_communication(self, request): } ), FileUpload.objects.filter(associating_id=communication["id"]), - ) + ), ), ] @@ -308,12 +314,13 @@ def send_communication(self, request): status=status.HTTP_400_BAD_REQUEST, ) - response = Hcx().generateOutgoingHcxCall( - fhirPayload=json.loads(communication_fhir_bundle.json()), + response = Hcx().generate_outgoing_hcx_call( + fhir_payload=json.loads(communication_fhir_bundle.json()), operation=HcxOperations.COMMUNICATION_ON_REQUEST, - recipientCode=communication["claim_object"]["policy_object"]["insurer_id"], - correlationId=Communication.objects.filter( - claim__external_id=communication["claim_object"]["id"], created_by=None + recipient_code=communication["claim_object"]["policy_object"]["insurer_id"], + correlation_id=Communication.objects.filter( + claim__external_id=communication["claim_object"]["id"], + created_by=None, ) .last() .identifier, @@ -324,7 +331,7 @@ def send_communication(self, request): @extend_schema(tags=["hcx"]) @action(detail=False, methods=["get"]) def payors(self, request): - payors = Hcx().searchRegistry("roles", "payor")["participants"] + payors = Hcx().search_registry("roles", "payor")["participants"] active_payors = list(filter(lambda payor: payor["status"] == "Active", payors)) @@ -335,7 +342,7 @@ def payors(self, request): "code": payor["participant_code"], }, active_payors, - ) + ), ) return Response(response, status=status.HTTP_200_OK) @@ -347,16 +354,18 @@ def pmjy_packages(self, request): def serailize_data(pmjy_packages): result = [] - for pmjy_package in pmjy_packages: - if type(pmjy_package) == tuple: - pmjy_package = pmjy_package[0] + for _pmjy_package in pmjy_packages: + if isinstance(_pmjy_package, tuple): + pmjy_package = _pmjy_package[0] + else: + pmjy_package = _pmjy_package result.append( { "code": pmjy_package.code, "name": pmjy_package.name, "price": pmjy_package.price, "package_name": pmjy_package.package_name, - } + }, ) return result @@ -368,6 +377,6 @@ def serailize_data(pmjy_packages): lambda row: search(r".*" + query + r".*", row.name, IGNORECASE) is not None or search(r".*" + query + r".*", row.package_name, IGNORECASE) - is not None + is not None, ) return Response(serailize_data(queryset[:limit])) diff --git a/care/hcx/api/viewsets/listener.py b/care/hcx/api/viewsets/listener.py index fd2f6922e3..59116bab0e 100644 --- a/care/hcx/api/viewsets/listener.py +++ b/care/hcx/api/viewsets/listener.py @@ -20,7 +20,7 @@ class CoverageElibilityOnCheckView(GenericAPIView): @extend_schema(tags=["hcx"]) def post(self, request, *args, **kwargs): - response = Hcx().processIncomingRequest(request.data["payload"]) + response = Hcx().process_incoming_request(request.data["payload"]) data = Fhir().process_coverage_elibility_check_response(response["payload"]) policy = Policy.objects.filter(external_id=data["id"]).first() @@ -34,7 +34,8 @@ def post(self, request, *args, **kwargs): "message": "success" if not data["error"] else "failed", } send_webpush( - username=policy.last_modified_by.username, message=json.dumps(message) + username=policy.last_modified_by.username, + message=json.dumps(message), ) return Response({}, status=status.HTTP_202_ACCEPTED) @@ -46,7 +47,7 @@ class PreAuthOnSubmitView(GenericAPIView): @extend_schema(tags=["hcx"]) def post(self, request, *args, **kwargs): - response = Hcx().processIncomingRequest(request.data["payload"]) + response = Hcx().process_incoming_request(request.data["payload"]) data = Fhir().process_claim_response(response["payload"]) claim = Claim.objects.filter(external_id=data["id"]).first() @@ -61,7 +62,8 @@ def post(self, request, *args, **kwargs): "message": "success" if not data["error"] else "failed", } send_webpush( - username=claim.last_modified_by.username, message=json.dumps(message) + username=claim.last_modified_by.username, + message=json.dumps(message), ) return Response({}, status=status.HTTP_202_ACCEPTED) @@ -73,7 +75,7 @@ class ClaimOnSubmitView(GenericAPIView): @extend_schema(tags=["hcx"]) def post(self, request, *args, **kwargs): - response = Hcx().processIncomingRequest(request.data["payload"]) + response = Hcx().process_incoming_request(request.data["payload"]) data = Fhir().process_claim_response(response["payload"]) claim = Claim.objects.filter(external_id=data["id"]).first() @@ -88,7 +90,8 @@ def post(self, request, *args, **kwargs): "message": "success" if not data["error"] else "failed", } send_webpush( - username=claim.last_modified_by.username, message=json.dumps(message) + username=claim.last_modified_by.username, + message=json.dumps(message), ) return Response({}, status=status.HTTP_202_ACCEPTED) @@ -100,7 +103,7 @@ class CommunicationRequestView(GenericAPIView): @extend_schema(tags=["hcx"]) def post(self, request, *args, **kwargs): - response = Hcx().processIncomingRequest(request.data["payload"]) + response = Hcx().process_incoming_request(request.data["payload"]) data = Fhir().process_communication_request(response["payload"]) claim = Claim.objects.filter(external_id__in=data["about"] or []).last() @@ -118,7 +121,8 @@ def post(self, request, *args, **kwargs): "message": f"{communication.external_id}", } send_webpush( - username=claim.last_modified_by.username, message=json.dumps(message) + username=claim.last_modified_by.username, + message=json.dumps(message), ) return Response({}, status=status.HTTP_202_ACCEPTED) diff --git a/care/hcx/models/__init__.py b/care/hcx/models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/care/hcx/models/claim.py b/care/hcx/models/claim.py index 6334e258c0..37f0838250 100644 --- a/care/hcx/models/claim.py +++ b/care/hcx/models/claim.py @@ -19,7 +19,8 @@ class Claim(BaseModel): consultation = models.ForeignKey(PatientConsultation, on_delete=models.CASCADE) policy = models.ForeignKey( - Policy, on_delete=models.CASCADE + Policy, + on_delete=models.CASCADE, ) # cascade - check it with Gigin items = JSONField(default=list, validators=[JSONFieldSchemaValidator(ITEMS)]) @@ -27,20 +28,38 @@ class Claim(BaseModel): total_amount_approved = models.FloatField(blank=True, null=True) use = models.CharField( - choices=USE_CHOICES, max_length=20, default=None, blank=True, null=True + choices=USE_CHOICES, + max_length=20, + default=None, + blank=True, + null=True, ) status = models.CharField( - choices=STATUS_CHOICES, max_length=20, default=None, blank=True, null=True + choices=STATUS_CHOICES, + max_length=20, + default=None, + blank=True, + null=True, ) priority = models.CharField( - choices=PRIORITY_CHOICES, max_length=20, default="normal" + choices=PRIORITY_CHOICES, + max_length=20, + default="normal", ) - type = models.CharField( - choices=CLAIM_TYPE_CHOICES, max_length=20, default=None, blank=True, null=True + type = models.CharField( # noqa: A003 + choices=CLAIM_TYPE_CHOICES, + max_length=20, + default=None, + blank=True, + null=True, ) outcome = models.CharField( - choices=OUTCOME_CHOICES, max_length=20, default=None, blank=True, null=True + choices=OUTCOME_CHOICES, + max_length=20, + default=None, + blank=True, + null=True, ) error_text = models.TextField(null=True, blank=True) diff --git a/care/hcx/models/communication.py b/care/hcx/models/communication.py index c0a8b2e6f2..fe77f007f3 100644 --- a/care/hcx/models/communication.py +++ b/care/hcx/models/communication.py @@ -12,7 +12,9 @@ class Communication(BaseModel): claim = models.ForeignKey(Claim, on_delete=models.CASCADE) content = models.JSONField( - default=list, validators=[JSONFieldSchemaValidator(CONTENT)], null=True + default=list, + validators=[JSONFieldSchemaValidator(CONTENT)], + null=True, ) created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) diff --git a/care/hcx/models/json_schema/__init__.py b/care/hcx/models/json_schema/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/care/hcx/models/json_schema/claim.py b/care/hcx/models/json_schema/claim.py index 091a843336..8037f379b6 100644 --- a/care/hcx/models/json_schema/claim.py +++ b/care/hcx/models/json_schema/claim.py @@ -12,6 +12,6 @@ }, "additionalProperties": False, "required": ["id", "name", "price"], - } + }, ], } diff --git a/care/hcx/models/json_schema/communication.py b/care/hcx/models/json_schema/communication.py index c120e477ec..b908d48c07 100644 --- a/care/hcx/models/json_schema/communication.py +++ b/care/hcx/models/json_schema/communication.py @@ -10,6 +10,6 @@ }, "additionalProperties": False, "required": ["type", "data"], - } + }, ], } diff --git a/care/hcx/models/policy.py b/care/hcx/models/policy.py index 1ee33474cf..ff0aa41435 100644 --- a/care/hcx/models/policy.py +++ b/care/hcx/models/policy.py @@ -21,17 +21,31 @@ class Policy(BaseModel): insurer_name = models.TextField(null=True, blank=True) status = models.CharField( - choices=STATUS_CHOICES, max_length=20, default=None, blank=True, null=True + choices=STATUS_CHOICES, + max_length=20, + default=None, + blank=True, + null=True, ) priority = models.CharField( - choices=PRIORITY_CHOICES, max_length=20, default="normal" + choices=PRIORITY_CHOICES, + max_length=20, + default="normal", ) purpose = models.CharField( - choices=PURPOSE_CHOICES, max_length=20, default=None, blank=True, null=True + choices=PURPOSE_CHOICES, + max_length=20, + default=None, + blank=True, + null=True, ) outcome = models.CharField( - choices=OUTCOME_CHOICES, max_length=20, default=None, blank=True, null=True + choices=OUTCOME_CHOICES, + max_length=20, + default=None, + blank=True, + null=True, ) error_text = models.TextField(null=True, blank=True) diff --git a/care/hcx/static_data/pmjy_packages.py b/care/hcx/static_data/pmjy_packages.py index 1520b44808..6e77458130 100644 --- a/care/hcx/static_data/pmjy_packages.py +++ b/care/hcx/static_data/pmjy_packages.py @@ -4,7 +4,7 @@ def fetch_data(): - with open("data/pmjy_packages.json", "r") as json_file: + with open("data/pmjy_packages.json") as json_file: return json.load(json_file) @@ -28,6 +28,8 @@ def fetch_data(): pmjy_package["price"] = pmjy_package.pop("procedure_price") PMJYPackages.insert(pmjy_package) +del pmjy_packages + PMJYPackages.create_search_index("name") PMJYPackages.create_search_index("package_name") PMJYPackages.create_index("code", unique=True) diff --git a/care/hcx/utils/__init__.py b/care/hcx/utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/care/hcx/utils/fhir.py b/care/hcx/utils/fhir.py index 1ecb041025..9de41815f0 100644 --- a/care/hcx/utils/fhir.py +++ b/care/hcx/utils/fhir.py @@ -1,6 +1,6 @@ -from datetime import datetime, timezone +from datetime import UTC, datetime from functools import reduce -from typing import List, Literal, TypedDict +from typing import Literal, TypedDict import requests from fhir.resources import ( @@ -258,7 +258,11 @@ def get_reference_url(self, resource: domainresource.DomainResource): return f"{resource.resource_type}/{resource.id}" def create_patient_profile( - self, id: str, name: str, gender: str, identifier_value: str + self, + id: str, + name: str, + gender: str, + identifier_value: str, ): return patient.Patient( id=id, @@ -271,12 +275,12 @@ def create_patient_profile( system=SYSTEM.codes, code="SN", display="Subscriber Number", - ) - ] + ), + ], ), system=SYSTEM.patient_identifier, value=identifier_value, - ) + ), ], name=[{"text": name}], gender=gender, @@ -294,12 +298,12 @@ def create_provider_profile(self, id: str, name: str, identifier_value: str): system=SYSTEM.codes, code="AC", display=name, - ) - ] + ), + ], ), system=SYSTEM.provider_identifier, value=identifier_value, - ) + ), ], name=name, ) @@ -316,18 +320,22 @@ def create_insurer_profile(self, id: str, name: str, identifier_value: str): system=SYSTEM.codes, code="AC", display=name, - ) - ] + ), + ], ), system=SYSTEM.insurer_identifier, value=identifier_value, - ) + ), ], name=name, ) def create_practitioner_role_profile( - self, id, identifier_value, speciality: str, phone: str + self, + id, + identifier_value, + speciality: str, + phone: str, ): return practitionerrole.PractitionerRole( id=id, @@ -340,11 +348,11 @@ def create_practitioner_role_profile( system=SYSTEM.codes, code="NP", display="Nurse practitioner number", - ) - ] + ), + ], ), value=identifier_value, - ) + ), ], specialty=[ codeableconcept.CodeableConcept( @@ -353,9 +361,9 @@ def create_practitioner_role_profile( system=SYSTEM.practitioner_speciality, code=speciality, display=PRACTIONER_SPECIALITY[speciality], - ) - ] - ) + ), + ], + ), ], telecom=[{"system": "phone", "value": phone}], ) @@ -375,8 +383,9 @@ def create_coverage_profile( meta=meta.Meta(profile=[PROFILE.coverage]), identifier=[ identifier.Identifier( - system=SYSTEM.coverage_identifier, value=identifier_value - ) + system=SYSTEM.coverage_identifier, + value=identifier_value, + ), ], status=status, subscriber=reference.Reference(reference=self.get_reference_url(patient)), @@ -387,8 +396,8 @@ def create_coverage_profile( coding.Coding( system=SYSTEM.coverage_relationship, code=relationship, - ) - ] + ), + ], ), payor=[reference.Reference(reference=self.get_reference_url(insurer))], ) @@ -405,9 +414,13 @@ def create_coverage_eligibility_request_profile( priority="normal", status="active", purpose="validation", - service_period_start=datetime.now().astimezone(tz=timezone.utc), - service_period_end=datetime.now().astimezone(tz=timezone.utc), + service_period_start: datetime | None = None, + service_period_end: datetime | None = None, ): + if service_period_start is None: + service_period_start = datetime.now().astimezone(tz=UTC) + if service_period_end is None: + service_period_end = datetime.now().astimezone(tz=UTC) return coverageeligibilityrequest.CoverageEligibilityRequest( id=id, meta=meta.Meta(profile=[PROFILE.coverage_eligibility_request]), @@ -418,8 +431,8 @@ def create_coverage_eligibility_request_profile( coding.Coding( system=SYSTEM.priority, code=priority, - ) - ] + ), + ], ), purpose=[purpose], patient=reference.Reference(reference=self.get_reference_url(patient)), @@ -427,16 +440,16 @@ def create_coverage_eligibility_request_profile( start=service_period_start, end=service_period_end, ), - created=datetime.now().astimezone(tz=timezone.utc), + created=datetime.now().astimezone(tz=UTC), enterer=reference.Reference(reference=self.get_reference_url(enterer)), provider=reference.Reference(reference=self.get_reference_url(provider)), insurer=reference.Reference(reference=self.get_reference_url(insurer)), insurance=[ coverageeligibilityrequest.CoverageEligibilityRequestInsurance( coverage=reference.Reference( - reference=self.get_reference_url(coverage) - ) - ) + reference=self.get_reference_url(coverage), + ), + ), ], ) @@ -444,21 +457,29 @@ def create_claim_profile( self, id: str, identifier_value: str, - items: List[IClaimItem], + items: list[IClaimItem], patient: patient.Patient, provider: organization.Organization, insurer: organization.Organization, coverage: coverage.Coverage, use="claim", status="active", - type="institutional", + claim_type="institutional", priority="normal", claim_payee_type="provider", - supporting_info=[], - related_claims=[], - procedures=[], - diagnoses=[], + supporting_info=None, + related_claims=None, + procedures=None, + diagnoses=None, ): + if diagnoses is None: + diagnoses = [] + if procedures is None: + procedures = [] + if related_claims is None: + related_claims = [] + if supporting_info is None: + supporting_info = [] return claim.Claim( id=id, meta=meta.Meta( @@ -466,17 +487,18 @@ def create_claim_profile( ), identifier=[ identifier.Identifier( - system=SYSTEM.claim_identifier, value=identifier_value - ) + system=SYSTEM.claim_identifier, + value=identifier_value, + ), ], status=status, type=codeableconcept.CodeableConcept( coding=[ coding.Coding( system=SYSTEM.claim_type, - code=type, - ) - ] + code=claim_type, + ), + ], ), use=use, related=list( @@ -489,19 +511,19 @@ def create_claim_profile( coding.Coding( system=SYSTEM.related_claim_relationship, code=related_claim["type"], - ) - ] + ), + ], ), claim=reference.Reference( - reference=f'Claim/{related_claim["id"]}' + reference=f'Claim/{related_claim["id"]}', ), ) ), related_claims, - ) + ), ), patient=reference.Reference(reference=self.get_reference_url(patient)), - created=datetime.now().astimezone(tz=timezone.utc), + created=datetime.now().astimezone(tz=UTC), insurer=reference.Reference(reference=self.get_reference_url(insurer)), provider=reference.Reference(reference=self.get_reference_url(provider)), priority=codeableconcept.CodeableConcept( @@ -509,8 +531,8 @@ def create_claim_profile( coding.Coding( system=SYSTEM.priority, code=priority, - ) - ] + ), + ], ), payee=claim.ClaimPayee( type=codeableconcept.CodeableConcept( @@ -518,8 +540,8 @@ def create_claim_profile( coding.Coding( system=SYSTEM.claim_payee_type, code=claim_payee_type, - ) - ] + ), + ], ), party=reference.Reference(reference=self.get_reference_url(provider)), ), @@ -527,18 +549,18 @@ def create_claim_profile( claim.ClaimCareTeam( sequence=1, provider=reference.Reference( - reference=self.get_reference_url(provider) + reference=self.get_reference_url(provider), ), - ) + ), ], insurance=[ claim.ClaimInsurance( sequence=1, focal=True, coverage=reference.Reference( - reference=self.get_reference_url(coverage) + reference=self.get_reference_url(coverage), ), - ) + ), ], item=list( map( @@ -551,8 +573,8 @@ def create_claim_profile( system=SYSTEM.claim_item, code=item["id"], display=item["name"], - ) - ] + ), + ], ), unitPrice={"value": item["price"], "currency": "INR"}, category=codeableconcept.CodeableConcept( @@ -562,14 +584,14 @@ def create_claim_profile( if item["category"] == "HBP" else SYSTEM.claim_item_category, code=item["category"], - ) - ] + ), + ], ), ) ), items, range(1, len(items) + 1), - ) + ), ), supportingInfo=list( map( @@ -581,8 +603,8 @@ def create_claim_profile( coding.Coding( system=SYSTEM.claim_supporting_info_category, code=info["type"], - ) - ] + ), + ], ), valueAttachment=attachment.Attachment( url=info["url"], @@ -592,7 +614,7 @@ def create_claim_profile( ), supporting_info, range(1, len(supporting_info) + 1), - ) + ), ), procedure=list( map( @@ -600,13 +622,13 @@ def create_claim_profile( claim.ClaimProcedure( sequence=i, procedureReference=reference.Reference( - reference=self.get_reference_url(procedure) + reference=self.get_reference_url(procedure), ), ) ), procedures, range(1, len(procedures) + 1), - ) + ), ), diagnosis=list( map( @@ -614,7 +636,7 @@ def create_claim_profile( claim.ClaimDiagnosis( sequence=i, diagnosisReference=reference.Reference( - reference=self.get_reference_url(diagnosis["profile"]) + reference=self.get_reference_url(diagnosis["profile"]), ), type=[ codeableconcept.CodeableConcept( @@ -622,15 +644,15 @@ def create_claim_profile( coding.Coding( system=SYSTEM.diagnosis_type, code=diagnosis["type"], - ) - ] - ) + ), + ], + ), ], ) ), diagnoses, range(1, len(diagnoses) + 1), - ) + ), ), ) @@ -654,9 +676,9 @@ def create_procedure_profile( performer=[ procedure.ProcedurePerformer( actor=reference.Reference( - reference=self.get_reference_url(provider) - ) - ) + reference=self.get_reference_url(provider), + ), + ), ], performedString=performed, ) @@ -667,8 +689,8 @@ def create_condition_profile(self, id, code, label, patient): # meta=meta.Meta(profile=[PROFILE.condition]), code=codeableconcept.CodeableConcept( coding=[ - coding.Coding(system=SYSTEM.condition, code=code, display=label) - ] + coding.Coding(system=SYSTEM.condition, code=code, display=label), + ], ), subject=reference.Reference(reference=self.get_reference_url(patient)), ) @@ -698,21 +720,37 @@ def create_coverage_eligibility_request_bundle( status="active", priority="normal", purpose="validation", - service_period_start=datetime.now().astimezone(tz=timezone.utc), - service_period_end=datetime.now().astimezone(tz=timezone.utc), - last_upadted=datetime.now().astimezone(tz=timezone.utc), + service_period_start: datetime | None = None, + service_period_end: datetime | None = None, + last_updated: datetime | None = None, ): + if service_period_start is None: + service_period_start = datetime.now().astimezone(tz=UTC) + if service_period_end is None: + service_period_end = datetime.now().astimezone(tz=UTC) + if last_updated is None: + last_updated = datetime.now().astimezone(tz=UTC) provider = self.create_provider_profile( - provider_id, provider_name, provider_identifier_value + provider_id, + provider_name, + provider_identifier_value, ) insurer = self.create_insurer_profile( - insurer_id, insurer_name, insurer_identifier_value + insurer_id, + insurer_name, + insurer_identifier_value, ) patient = self.create_patient_profile( - patient_id, pateint_name, patient_gender, subscriber_id + patient_id, + pateint_name, + patient_gender, + subscriber_id, ) enterer = self.create_practitioner_role_profile( - enterer_id, enterer_identifier_value, enterer_speciality, enterer_phone + enterer_id, + enterer_identifier_value, + enterer_speciality, + enterer_phone, ) coverage = self.create_coverage_profile( coverage_id, @@ -740,7 +778,7 @@ def create_coverage_eligibility_request_bundle( return bundle.Bundle( id=id, meta=meta.Meta( - lastUpdated=last_upadted, + lastUpdated=last_updated, profile=[PROFILE.coverage_eligibility_request_bundle], ), identifier=identifier.Identifier( @@ -748,7 +786,7 @@ def create_coverage_eligibility_request_bundle( value=identifier_value, ), type="collection", - timestamp=datetime.now().astimezone(tz=timezone.utc), + timestamp=datetime.now().astimezone(tz=UTC), entry=[ bundle.BundleEntry( fullUrl=self.get_reference_url(coverage_eligibility_request), @@ -794,23 +832,40 @@ def create_claim_bundle( items: list[IClaimItem], use="claim", status="active", - type="institutional", + claim_type="institutional", priority="normal", claim_payee_type="provider", - last_updated=datetime.now().astimezone(tz=timezone.utc), - supporting_info=[], - related_claims=[], - procedures=[], - diagnoses=[], + last_updated: datetime | None = None, + supporting_info=None, + related_claims=None, + procedures=None, + diagnoses=None, ): + if last_updated is None: + last_updated = datetime.now().astimezone(tz=UTC) + if diagnoses is None: + diagnoses = [] + if procedures is None: + procedures = [] + if related_claims is None: + related_claims = [] + if supporting_info is None: + supporting_info = [] provider = self.create_provider_profile( - provider_id, provider_name, provider_identifier_value + provider_id, + provider_name, + provider_identifier_value, ) insurer = self.create_insurer_profile( - insurer_id, insurer_name, insurer_identifier_value + insurer_id, + insurer_name, + insurer_identifier_value, ) patient = self.create_patient_profile( - patient_id, pateint_name, patient_gender, subscriber_id + patient_id, + pateint_name, + patient_gender, + subscriber_id, ) coverage = self.create_coverage_profile( coverage_id, @@ -832,19 +887,22 @@ def create_claim_bundle( procedure["performed"], ), procedures, - ) + ), ) diagnoses = list( map( lambda diagnosis: { "profile": self.create_condition_profile( - diagnosis["id"], diagnosis["code"], diagnosis["label"], patient + diagnosis["id"], + diagnosis["code"], + diagnosis["label"], + patient, ), "type": diagnosis["type"], }, diagnoses, - ) + ), ) claim = self.create_claim_profile( @@ -857,7 +915,7 @@ def create_claim_bundle( coverage, use, status, - type, + claim_type, priority, claim_payee_type, supporting_info=supporting_info, @@ -877,7 +935,7 @@ def create_claim_bundle( value=identifier_value, ), type="collection", - timestamp=datetime.now().astimezone(tz=timezone.utc), + timestamp=datetime.now().astimezone(tz=UTC), entry=[ bundle.BundleEntry( fullUrl=self.get_reference_url(claim), @@ -906,7 +964,7 @@ def create_claim_bundle( resource=procedure, ), procedures, - ) + ), ), *list( map( @@ -915,7 +973,7 @@ def create_claim_bundle( resource=diagnosis["profile"], ), diagnoses, - ) + ), ), ], ) @@ -926,14 +984,17 @@ def create_communication_profile( identifier_value: str, payload: list, about: list, - last_updated=datetime.now().astimezone(tz=timezone.utc), + last_updated: datetime | None = None, ): + if last_updated is None: + last_updated = datetime.now().astimezone(tz=UTC) return communication.Communication( id=id, identifier=[ identifier.Identifier( - system=SYSTEM.communication_identifier, value=identifier_value - ) + system=SYSTEM.communication_identifier, + value=identifier_value, + ), ], meta=meta.Meta(lastUpdated=last_updated, profile=[PROFILE.communication]), status="completed", @@ -941,7 +1002,7 @@ def create_communication_profile( map( lambda ref: (reference.Reference(type=ref["type"], id=ref["id"])), about, - ) + ), ), payload=list( map( @@ -959,7 +1020,7 @@ def create_communication_profile( ) ), payload, - ) + ), ), ) @@ -971,8 +1032,11 @@ def create_communication_bundle( communication_identifier_value: str, payload: list, about: list, - last_updated=datetime.now().astimezone(tz=timezone.utc), + last_updated: datetime | None = None, ): + if last_updated is None: + last_updated = datetime.now().astimezone(tz=UTC) + communication_profile = self.create_communication_profile( communication_id, communication_identifier_value, @@ -992,7 +1056,7 @@ def create_communication_bundle( value=identifier_value, ), type="collection", - timestamp=datetime.now().astimezone(tz=timezone.utc), + timestamp=datetime.now().astimezone(tz=UTC), entry=[ bundle.BundleEntry( fullUrl=self.get_reference_url(communication_profile), @@ -1011,8 +1075,8 @@ def process_coverage_elibility_check_response(self, response): lambda entry: type(entry.resource) == coverageeligibilityresponse.CoverageEligibilityResponse, coverage_eligibility_check_bundle.entry, - ) - )[0].resource.dict() + ), + )[0].resource.dict(), ) ) coverage_request = coverage.Coverage( @@ -1020,13 +1084,13 @@ def process_coverage_elibility_check_response(self, response): filter( lambda entry: type(entry.resource) == coverage.Coverage, coverage_eligibility_check_bundle.entry, - ) - )[0].resource.dict() + ), + )[0].resource.dict(), ) def get_errors_from_coding(codings): return "; ".join( - list(map(lambda coding: f"{coding.code}: {coding.display}", codings)) + list(map(lambda coding: f"{coding.code}: {coding.display}", codings)), ) return { @@ -1037,8 +1101,8 @@ def get_errors_from_coding(codings): map( lambda error: get_errors_from_coding(error.code.coding), coverage_eligibility_check_response.error or [], - ) - ) + ), + ), ), } @@ -1050,13 +1114,13 @@ def process_claim_response(self, response): filter( lambda entry: type(entry.resource) == claimresponse.ClaimResponse, claim_bundle.entry, - ) - )[0].resource.dict() + ), + )[0].resource.dict(), ) def get_errors_from_coding(codings): return "; ".join( - list(map(lambda coding: f"{coding.code}: {coding.display}", codings)) + list(map(lambda coding: f"{coding.code}: {coding.display}", codings)), ) return { @@ -1065,7 +1129,7 @@ def get_errors_from_coding(codings): lambda price, acc: price + acc, map( lambda claim_response_total: float( - claim_response_total.amount.value + claim_response_total.amount.value, ), claim_response.total, ), @@ -1077,12 +1141,12 @@ def get_errors_from_coding(codings): map( lambda error: get_errors_from_coding(error.code.coding), claim_response.error or [], - ) - ) + ), + ), ), } - def process_communication_request(self, request): + def process_communication_request(self, request): # noqa: PLR0912 communication_request = communicationrequest.CommunicationRequest(**request) data = { @@ -1097,8 +1161,8 @@ def process_communication_request(self, request): if communication_request.about: data["about"] = [] - for object in communication_request.about: - about = reference.Reference(**object.dict()) + for obj in communication_request.about: + about = reference.Reference(**obj.dict()) if about.identifier: id = identifier.Identifier(about.identifier).value data["about"].append(id) @@ -1111,8 +1175,8 @@ def process_communication_request(self, request): if communication_request.basedOn: data["based_on"] = [] - for object in communication_request.basedOn: - based_on = reference.Reference(**object.dict()) + for obj in communication_request.basedOn: + based_on = reference.Reference(**obj.dict()) if based_on.identifier: id = identifier.Identifier(based_on.identifier).value data["based_on"].append(id) @@ -1125,14 +1189,14 @@ def process_communication_request(self, request): if communication_request.payload: data["payload"] = [] - for object in communication_request.payload: + for obj in communication_request.payload: payload = communicationrequest.CommunicationRequestPayload( - **object.dict() + **obj.dict(), ) if payload.contentString: data["payload"].append( - {"type": "text", "data": payload.contentString} + {"type": "text", "data": payload.contentString}, ) continue @@ -1143,16 +1207,16 @@ def process_communication_request(self, request): { "type": content.contentType or "text", "data": content.data, - } + }, ) elif content.url: data["payload"].append({"type": "url", "data": content.url}) return data - def validate_fhir_local(self, fhir_payload, type="bundle"): + def validate_fhir_local(self, fhir_payload, payload_type="bundle"): try: - if type == "bundle": + if payload_type == "bundle": bundle.Bundle(**fhir_payload) except Exception as e: return {"valid": False, "issues": [e]} @@ -1162,7 +1226,10 @@ def validate_fhir_local(self, fhir_payload, type="bundle"): def validate_fhir_remote(self, fhir_payload): headers = {"Content-Type": "application/json"} response = requests.request( - "POST", FHIR_VALIDATION_URL, headers=headers, data=fhir_payload + "POST", + FHIR_VALIDATION_URL, + headers=headers, + data=fhir_payload, ).json() issues = response["issue"] if "issue" in response else [] diff --git a/care/hcx/utils/hcx/__init__.py b/care/hcx/utils/hcx/__init__.py index 533e0a9bbc..a922bf6647 100644 --- a/care/hcx/utils/hcx/__init__.py +++ b/care/hcx/utils/hcx/__init__.py @@ -1,5 +1,6 @@ import datetime import json +import logging import uuid from urllib.parse import urlencode @@ -7,28 +8,32 @@ from django.conf import settings from jwcrypto import jwe, jwk +logger = logging.getLogger(__name__) + +HCX_API_TIMEOUT = 25 + class Hcx: def __init__( self, - protocolBasePath=settings.HCX_PROTOCOL_BASE_PATH, - participantCode=settings.HCX_PARTICIPANT_CODE, - authBasePath=settings.HCX_AUTH_BASE_PATH, + protocol_base_path=settings.HCX_PROTOCOL_BASE_PATH, + participant_code=settings.HCX_PARTICIPANT_CODE, + auth_base_path=settings.HCX_AUTH_BASE_PATH, username=settings.HCX_USERNAME, password=settings.HCX_PASSWORD, - encryptionPrivateKeyURL=settings.HCX_ENCRYPTION_PRIVATE_KEY_URL, - igUrl=settings.HCX_IG_URL, + encryption_private_key_url=settings.HCX_ENCRYPTION_PRIVATE_KEY_URL, + ig_url=settings.HCX_IG_URL, ): - self.protocolBasePath = protocolBasePath - self.participantCode = participantCode - self.authBasePath = authBasePath + self.protocol_base_path = protocol_base_path + self.participant_code = participant_code + self.auth_base_path = auth_base_path self.username = username self.password = password - self.encryptionPrivateKeyURL = encryptionPrivateKeyURL - self.igUrl = igUrl + self.encryption_private_key_url = encryption_private_key_url + self.ig_url = ig_url - def generateHcxToken(self): - url = self.authBasePath + def generate_hcx_token(self): + url = self.auth_base_path payload = { "client_id": "registry-frontend", @@ -40,15 +45,18 @@ def generateHcxToken(self): headers = {"content-type": "application/x-www-form-urlencoded"} response = requests.request( - "POST", url, headers=headers, data=payload_urlencoded + "POST", + url, + headers=headers, + data=payload_urlencoded, ) y = json.loads(response.text) return y["access_token"] - def searchRegistry(self, searchField, searchValue): - url = self.protocolBasePath + "/participant/search" - access_token = self.generateHcxToken() - payload = json.dumps({"filters": {searchField: {"eq": searchValue}}}) + def search_registry(self, search_field, search_value): + url = self.protocol_base_path + "/participant/search" + access_token = self.generate_hcx_token() + payload = json.dumps({"filters": {search_field: {"eq": search_value}}}) headers = { "Authorization": "Bearer " + access_token, "Content-Type": "application/json", @@ -57,62 +65,64 @@ def searchRegistry(self, searchField, searchValue): response = requests.request("POST", url, headers=headers, data=payload) return dict(json.loads(response.text)) - def createHeaders(self, recipientCode=None, correlationId=None): - # creating HCX headers - # getting sender code - # regsitry_user = self.searchRegistry("primary_email", self.username) - hcx_headers = { + def create_headers(self, recipient_code=None, correlation_id=None): + return { "alg": "RSA-OAEP", "enc": "A256GCM", - "x-hcx-recipient_code": recipientCode, + "x-hcx-recipient_code": recipient_code, "x-hcx-timestamp": datetime.datetime.now() .astimezone() .replace(microsecond=0) .isoformat(), - "x-hcx-sender_code": self.participantCode, - "x-hcx-correlation_id": correlationId - if correlationId + "x-hcx-sender_code": self.participant_code, + "x-hcx-correlation_id": correlation_id + if correlation_id else str(uuid.uuid4()), # "x-hcx-workflow_id": str(uuid.uuid4()), "x-hcx-api_call_id": str(uuid.uuid4()), # "x-hcx-status": "response.complete", } - return hcx_headers - def encryptJWE(self, recipientCode=None, fhirPayload=None, correlationId=None): - if recipientCode is None: + def encrypt_jwe(self, recipient_code=None, fhir_payload=None, correlation_id=None): + if recipient_code is None: raise ValueError("Recipient code can not be empty, must be a string") - if type(fhirPayload) is not dict: + if not isinstance(fhir_payload, dict): raise ValueError("Fhir paylaod must be a dictionary") - regsitry_data = self.searchRegistry( - searchField="participant_code", searchValue=recipientCode + regsitry_data = self.search_registry( + search_field="participant_code", + search_value=recipient_code, + ) + response = requests.get( + regsitry_data["participants"][0]["encryption_cert"], + timeout=HCX_API_TIMEOUT, ) - public_cert = requests.get(regsitry_data["participants"][0]["encryption_cert"]) - key = jwk.JWK.from_pem(public_cert.text.encode("utf-8")) - headers = self.createHeaders(recipientCode, correlationId) - jwePayload = jwe.JWE( - str(json.dumps(fhirPayload)), + key = jwk.JWK.from_pem(response.text.encode("utf-8")) + headers = self.create_headers(recipient_code, correlation_id) + jwe_payload = jwe.JWE( + str(json.dumps(fhir_payload)), recipient=key, protected=json.dumps(headers), ) - enc = jwePayload.serialize(compact=True) - return enc - - def decryptJWE(self, encryptedString): - private_key = requests.get(self.encryptionPrivateKeyURL) - privateKey = jwk.JWK.from_pem(private_key.text.encode("utf-8")) - jwetoken = jwe.JWE() - jwetoken.deserialize(encryptedString, key=privateKey) + return jwe_payload.serialize(compact=True) + + def decrypt_jwe(self, encrypted_string): + response = requests.get( + self.encryption_private_key_url, + timeout=HCX_API_TIMEOUT, + ) + private_key = jwk.JWK.from_pem(response.text.encode("utf-8")) + jwe_token = jwe.JWE() + jwe_token.deserialize(encrypted_string, key=private_key) return { - "headers": dict(json.loads(jwetoken.payload.decode("utf-8"))), - "payload": dict(json.loads(jwetoken.payload.decode("utf-8"))), + "headers": dict(json.loads(jwe_token.payload.decode("utf-8"))), + "payload": dict(json.loads(jwe_token.payload.decode("utf-8"))), } - def makeHcxApiCall(self, operation, encryptedString): - url = "".join(self.protocolBasePath + operation.value) - print("making the API call to url " + url) - access_token = self.generateHcxToken() - payload = json.dumps({"payload": encryptedString}) + def make_hcx_api_call(self, operation, encrypted_string): + url = "".join(self.protocol_base_path + operation.value) + logger.info("making hxc api call to url: %s", url) + access_token = self.generate_hcx_token() + payload = json.dumps({"payload": encrypted_string}) headers = { "Authorization": "Bearer " + access_token, "Content-Type": "application/json", @@ -120,18 +130,23 @@ def makeHcxApiCall(self, operation, encryptedString): response = requests.request("POST", url, headers=headers, data=payload) return dict(json.loads(response.text)) - def generateOutgoingHcxCall( - self, fhirPayload, operation, recipientCode, correlationId=None + def generate_outgoing_hcx_call( + self, + fhir_payload, + operation, + recipient_code, + correlation_id=None, ): - encryptedString = self.encryptJWE( - recipientCode=recipientCode, - fhirPayload=fhirPayload, - correlationId=correlationId, + encrypted_string = self.encrypt_jwe( + recipient_code=recipient_code, + fhir_payload=fhir_payload, + correlation_id=correlation_id, ) - response = self.makeHcxApiCall( - operation=operation, encryptedString=encryptedString + response = self.make_hcx_api_call( + operation=operation, + encrypted_string=encrypted_string, ) - return {"payload": encryptedString, "response": response} + return {"payload": encrypted_string, "response": response} - def processIncomingRequest(self, encryptedString): - return self.decryptJWE(encryptedString=encryptedString) + def process_incoming_request(self, encrypted_string): + return self.decrypt_jwe(encrypted_string=encrypted_string) diff --git a/care/users/admin.py b/care/users/admin.py index 4b02b5e568..c451a4a427 100644 --- a/care/users/admin.py +++ b/care/users/admin.py @@ -12,7 +12,7 @@ class ExportCsvMixin: def export_as_csv(self, request, queryset): queryset = User.objects.filter(is_superuser=False).values( - *User.CSV_MAPPING.keys() + *User.CSV_MAPPING.keys(), ) return render_to_csv_response( queryset, @@ -42,7 +42,7 @@ class UserAdmin(auth_admin.UserAdmin, ExportCsvMixin): "gender", "age", "verified", - ) + ), }, ), ) + auth_admin.UserAdmin.fieldsets diff --git a/config/auth_views.py b/care/users/api/serializers/auth.py similarity index 71% rename from config/auth_views.py rename to care/users/api/serializers/auth.py index 36412d618d..0f3704d62c 100644 --- a/config/auth_views.py +++ b/care/users/api/serializers/auth.py @@ -1,30 +1,25 @@ +import contextlib + from django.contrib.auth import authenticate, get_user_model from django.utils.timezone import localtime, now from django.utils.translation import gettext_lazy as _ -from drf_spectacular.utils import extend_schema from rest_framework import serializers, status from rest_framework_simplejwt.exceptions import AuthenticationFailed from rest_framework_simplejwt.serializers import PasswordField from rest_framework_simplejwt.settings import api_settings from rest_framework_simplejwt.tokens import RefreshToken -from rest_framework_simplejwt.views import TokenVerifyView, TokenViewBase +from care.utils.exceptions import CaptchaRequiredException from config.ratelimit import ratelimit User = get_user_model() -class CaptchaRequiredException(AuthenticationFailed): - status_code = status.HTTP_429_TOO_MANY_REQUESTS - default_detail = _("Too Many Requests Provide Captcha") - default_code = "captchaRequired" - - class TokenObtainSerializer(serializers.Serializer): username_field = User.USERNAME_FIELD default_error_messages = { - "no_active_account": _("No active account found with the given credentials") + "no_active_account": _("No active account found with the given credentials"), } def __init__(self, *args, **kwargs): @@ -38,10 +33,9 @@ def validate(self, attrs): self.username_field: attrs[self.username_field], "password": attrs["password"], } - try: + with contextlib.suppress(KeyError): authenticate_kwargs["request"] = self.context["request"] - except KeyError: - pass + if ratelimit( self.context["request"], "login", @@ -74,7 +68,7 @@ def validate(self, attrs): @classmethod def get_token(cls, user): raise NotImplementedError( - "Must implement `get_token` method for `TokenObtainSerializer` subclasses" + "Must implement `get_token` method for `TokenObtainSerializer` subclasses", ) @@ -104,7 +98,7 @@ def validate(self, attrs): # Updating users active status User.objects.filter(external_id=refresh["user_id"]).update( - last_login=localtime(now()) + last_login=localtime(now()), ) return data @@ -128,46 +122,3 @@ def validate(self, attrs): User.objects.filter(id=self.user.id).update(last_login=localtime(now())) return data - - -class TokenObtainPairView(TokenViewBase): - """ - Generate access and refresh tokens for a user. - - Takes a set of user credentials and returns an access and refresh JSON web - token pair to prove the authentication of those credentials. - """ - - serializer_class = TokenObtainPairSerializer - - @extend_schema(tags=["auth"]) - def post(self, request, *args, **kwargs): - return super().post(request, *args, **kwargs) - - -class TokenRefreshView(TokenViewBase): - """ - Refresh access token. - - Takes a refresh type JSON web token and returns an access type JSON web - token if the refresh token is valid. - """ - - serializer_class = TokenRefreshSerializer - - @extend_schema(tags=["auth"]) - def post(self, request, *args, **kwargs): - return super().post(request, *args, **kwargs) - - -class AnnotatedTokenVerifyView(TokenVerifyView): - """ - Verify tokens are valid. - - Takes a token and returns a boolean of whether it is a valid JSON web token - for this project. - """ - - @extend_schema(tags=["auth"]) - def post(self, request, *args, **kwargs): - return super().post(request, *args, **kwargs) diff --git a/care/users/api/serializers/user.py b/care/users/api/serializers/user.py index 0613b3437f..5098ee26b8 100644 --- a/care/users/api/serializers/user.py +++ b/care/users/api/serializers/user.py @@ -1,8 +1,7 @@ -from datetime import date - from django.contrib.auth import get_user_model from django.contrib.auth.hashers import make_password from django.db import transaction +from django.utils import timezone from rest_framework import exceptions, serializers from care.facility.api.serializers.facility import FacilityBareMinimumSerializer @@ -70,7 +69,7 @@ def validate(self, attrs): }, ) - if attrs["doctor_experience_commenced_on"] > date.today(): + if attrs["doctor_experience_commenced_on"] > timezone.now().date(): raise serializers.ValidationError( { "doctor_experience_commenced_on": "Experience cannot be in the future", @@ -117,18 +116,17 @@ class Meta: ) def validate_facilities(self, facility_ids): - if facility_ids: - if ( - len(facility_ids) - != Facility.objects.filter(external_id__in=facility_ids).count() - ): - available_facility_ids = Facility.objects.filter( - external_id__in=facility_ids, - ).values_list("external_id", flat=True) - not_found_ids = list(set(facility_ids) - set(available_facility_ids)) - raise serializers.ValidationError( - f"Some facilities are not available - {', '.join([str(_id) for _id in not_found_ids])}", - ) + if facility_ids and ( + len(facility_ids) + != Facility.objects.filter(external_id__in=facility_ids).count() + ): + available_facility_ids = Facility.objects.filter( + external_id__in=facility_ids, + ).values_list("external_id", flat=True) + not_found_ids = list(set(facility_ids) - set(available_facility_ids)) + raise serializers.ValidationError( + f"Some facilities are not available - {', '.join([str(_id) for _id in not_found_ids])}", + ) return facility_ids def validate_ward(self, value): @@ -186,15 +184,17 @@ def validate(self, attrs): }, ) - if self.context["created_by"].user_type in READ_ONLY_USER_TYPES: - if validated["user_type"] not in READ_ONLY_USER_TYPES: - raise exceptions.ValidationError( - { - "user_type": [ - "Read only users can create other read only users only", - ], - }, - ) + if ( + self.context["created_by"].user_type in READ_ONLY_USER_TYPES + and validated["user_type"] not in READ_ONLY_USER_TYPES + ): + raise exceptions.ValidationError( + { + "user_type": [ + "Read only users can create other read only users only", + ], + }, + ) if ( self.context["created_by"].user_type == User.TYPE_VALUE_MAP["Staff"] diff --git a/care/users/api/serializers/userskill.py b/care/users/api/serializers/userskill.py index 0d75904700..b4fa93eb1b 100644 --- a/care/users/api/serializers/userskill.py +++ b/care/users/api/serializers/userskill.py @@ -8,7 +8,9 @@ class UserSkillSerializer(ModelSerializer): id = UUIDField(source="external_id", read_only=True) skill = ExternalIdSerializerField( - write_only=True, required=True, queryset=Skill.objects.all() + write_only=True, + required=True, + queryset=Skill.objects.all(), ) skill_object = SkillSerializer(source="skill", read_only=True) diff --git a/care/users/api/viewsets/auth.py b/care/users/api/viewsets/auth.py new file mode 100644 index 0000000000..0bc56509d0 --- /dev/null +++ b/care/users/api/viewsets/auth.py @@ -0,0 +1,53 @@ +from django.contrib.auth import get_user_model +from drf_spectacular.utils import extend_schema +from rest_framework_simplejwt.views import TokenVerifyView, TokenViewBase + +from care.users.api.serializers.auth import ( + TokenObtainPairSerializer, + TokenRefreshSerializer, +) + +User = get_user_model() + + +class TokenObtainPairView(TokenViewBase): + """ + Generate access and refresh tokens for a user. + + Takes a set of user credentials and returns an access and refresh JSON web + token pair to prove the authentication of those credentials. + """ + + serializer_class = TokenObtainPairSerializer + + @extend_schema(tags=["auth"]) + def post(self, request, *args, **kwargs): + return super().post(request, *args, **kwargs) + + +class TokenRefreshView(TokenViewBase): + """ + Refresh access token. + + Takes a refresh type JSON web token and returns an access type JSON web + token if the refresh token is valid. + """ + + serializer_class = TokenRefreshSerializer + + @extend_schema(tags=["auth"]) + def post(self, request, *args, **kwargs): + return super().post(request, *args, **kwargs) + + +class AnnotatedTokenVerifyView(TokenVerifyView): + """ + Verify tokens are valid. + + Takes a token and returns a boolean of whether it is a valid JSON web token + for this project. + """ + + @extend_schema(tags=["auth"]) + def post(self, request, *args, **kwargs): + return super().post(request, *args, **kwargs) diff --git a/care/users/api/viewsets/change_password.py b/care/users/api/viewsets/change_password.py index 806eb0a414..16d6ca6953 100644 --- a/care/users/api/viewsets/change_password.py +++ b/care/users/api/viewsets/change_password.py @@ -40,8 +40,8 @@ def update(self, request, *args, **kwargs): return Response( { "old_password": [ - "Wrong password entered. Please check your password." - ] + "Wrong password entered. Please check your password.", + ], }, status=status.HTTP_400_BAD_REQUEST, ) diff --git a/care/users/api/viewsets/lsg.py b/care/users/api/viewsets/lsg.py index 2b45ccbb35..49f9cd523a 100644 --- a/care/users/api/viewsets/lsg.py +++ b/care/users/api/viewsets/lsg.py @@ -43,7 +43,8 @@ class StateViewSet( def districts(self, *args, **kwargs): state = self.get_object() serializer = DistrictSerializer( - state.district_set.all().order_by("name"), many=True + state.district_set.all().order_by("name"), + many=True, ) return Response(data=serializer.data) @@ -77,7 +78,8 @@ class DistrictViewSet( def local_bodies(self, *args, **kwargs): district = self.get_object() serializer = LocalBodySerializer( - district.localbody_set.all().order_by("name"), many=True + district.localbody_set.all().order_by("name"), + many=True, ) return Response(data=serializer.data) @@ -90,7 +92,8 @@ def get_all_local_body(self, *args, **kwargs): for lsg_object in LocalBody.objects.filter(district=district): local_body_object = LocalBodySerializer(lsg_object).data local_body_object["wards"] = WardSerializer( - Ward.objects.filter(local_body=lsg_object), many=True + Ward.objects.filter(local_body=lsg_object), + many=True, ).data data.append(local_body_object) return Response(data) @@ -99,11 +102,13 @@ def get_all_local_body(self, *args, **kwargs): class LocalBodyFilterSet(filters.FilterSet): state = filters.NumberFilter(field_name="district__state_id") state_name = filters.CharFilter( - field_name="district__state__name", lookup_expr="icontains" + field_name="district__state__name", + lookup_expr="icontains", ) district = filters.NumberFilter(field_name="district_id") district_name = filters.CharFilter( - field_name="district__name", lookup_expr="icontains" + field_name="district__name", + lookup_expr="icontains", ) local_body_name = filters.CharFilter(field_name="name", lookup_expr="icontains") @@ -129,15 +134,18 @@ class LocalBodyViewSet( class WardFilterSet(filters.FilterSet): state = filters.NumberFilter(field_name="district__state_id") state_name = filters.CharFilter( - field_name="district__state__name", lookup_expr="icontains" + field_name="district__state__name", + lookup_expr="icontains", ) district = filters.NumberFilter(field_name="local_body__district_id") district_name = filters.CharFilter( - field_name="local_body__district__name", lookup_expr="icontains" + field_name="local_body__district__name", + lookup_expr="icontains", ) local_body = filters.NumberFilter(field_name="local_body_id") local_body_name = filters.CharFilter( - field_name="local_body__name", lookup_expr="icontains" + field_name="local_body__name", + lookup_expr="icontains", ) ward_name = filters.CharFilter(field_name="name", lookup_expr="icontains") diff --git a/care/users/api/viewsets/users.py b/care/users/api/viewsets/users.py index 9d590b546d..a84d45dc73 100644 --- a/care/users/api/viewsets/users.py +++ b/care/users/api/viewsets/users.py @@ -48,15 +48,17 @@ class UserFilterSet(filters.FilterSet): last_name = filters.CharFilter(field_name="last_name", lookup_expr="icontains") username = filters.CharFilter(field_name="username", lookup_expr="icontains") phone_number = filters.CharFilter( - field_name="phone_number", lookup_expr="icontains" + field_name="phone_number", + lookup_expr="icontains", ) alt_phone_number = filters.CharFilter( - field_name="alt_phone_number", lookup_expr="icontains" + field_name="alt_phone_number", + lookup_expr="icontains", ) last_login = filters.DateFromToRangeFilter(field_name="last_login") district_id = filters.NumberFilter(field_name="district_id", lookup_expr="exact") home_facility = filters.UUIDFilter( - field_name="home_facility__external_id", lookup_expr="exact" + field_name="home_facility__external_id", lookup_expr="exact", ) def get_user_type( @@ -65,9 +67,8 @@ def get_user_type( field_name, value, ): - if value: - if value in INVERSE_USER_TYPE: - return queryset.filter(user_type=INVERSE_USER_TYPE[value]) + if value and value in INVERSE_USER_TYPE: + return queryset.filter(user_type=INVERSE_USER_TYPE[value]) return queryset user_type = filters.CharFilter(method="get_user_type", field_name="user_type") @@ -106,31 +107,13 @@ class UserViewSet( filterset_class = UserFilterSet ordering_fields = ["id", "date_joined", "last_login"] search_fields = ["first_name", "last_name", "username"] - # last_login - # def get_permissions(self): - # return [ - # DRYPermissions(), - # IsAuthenticated(), - # ] - # if self.request.method == "POST": - # return [ - # DRYPermissions(), - # ] - # else: - # return [ - # IsAuthenticated(), - # DRYPermissions(), - # ] def get_serializer_class(self): if self.action == "list": return UserListSerializer - elif self.action == "add_user": + if self.action == "add_user": return UserCreateSerializer - # elif self.action == "create": - # return SignUpSerializer - else: - return UserSerializer + return UserSerializer @extend_schema(tags=["users"]) @action(detail=False, methods=["GET"]) @@ -153,7 +136,8 @@ def destroy(self, request, *args, **kwargs): ) else: return Response( - status=status.HTTP_403_FORBIDDEN, data={"permission": "Denied"} + status=status.HTTP_403_FORBIDDEN, + data={"permission": "Denied"}, ) user = get_object_or_404(queryset.filter(username=username)) user.is_active = False @@ -164,7 +148,8 @@ def destroy(self, request, *args, **kwargs): @action(detail=False, methods=["POST"]) def add_user(self, request, *args, **kwargs): password = request.data.pop( - "password", User.objects.make_random_password(length=8) + "password", + User.objects.make_random_password(length=8), ) serializer = UserCreateSerializer( data={**request.data, "password": password}, @@ -203,7 +188,10 @@ def check_facility_user_exists(self, user, facility): def get_facilities(self, request, *args, **kwargs): user = self.get_object() facilities = Facility.objects.filter(users=user).select_related( - "local_body", "district", "state", "ward" + "local_body", + "district", + "state", + "ward", ) facilities = FacilityBasicInfoSerializer(facilities, many=True) return Response(facilities.data) @@ -227,7 +215,7 @@ def add_facility(self, request, *args, **kwargs): raise ValidationError({"facility": "Facility Access not Present"}) if self.check_facility_user_exists(user, facility): raise ValidationError( - {"facility": "User Already has permission to this facility"} + {"facility": "User Already has permission to this facility"}, ) FacilityUser(facility=facility, user=user, created_by=requesting_user).save() return Response(status=status.HTTP_201_CREATED) @@ -276,7 +264,7 @@ def delete_facility(self, request, *args, **kwargs): raise ValidationError({"facility": "Facility Access not Present"}) if not self.has_facility_permission(user, facility): raise ValidationError( - {"facility": "Intended User Does not have permission to this facility"} + {"facility": "Intended User Does not have permission to this facility"}, ) if user.home_facility == facility: raise ValidationError({"facility": "Cannot Delete User's Home Facility"}) @@ -297,7 +285,7 @@ def pnconfig(self, request, *args, **kwargs): "pf_endpoint": user.pf_endpoint, "pf_p256dh": user.pf_p256dh, "pf_auth": user.pf_auth, - } + }, ) acceptable_fields = ["pf_endpoint", "pf_p256dh", "pf_auth"] for field in acceptable_fields: diff --git a/care/users/forms.py b/care/users/forms.py index cf4b25a56a..cb99752e32 100644 --- a/care/users/forms.py +++ b/care/users/forms.py @@ -12,7 +12,7 @@ class Meta(forms.UserChangeForm.Meta): class UserCreationForm(forms.UserCreationForm): error_message = forms.UserCreationForm.error_messages.update( - {"duplicate_username": _("This username has already been taken.")} + {"duplicate_username": _("This username has already been taken.")}, ) class Meta(forms.UserCreationForm.Meta): diff --git a/care/users/management/commands/load_data.py b/care/users/management/commands/load_data.py index 02a444fae5..69c073b1d8 100644 --- a/care/users/management/commands/load_data.py +++ b/care/users/management/commands/load_data.py @@ -8,7 +8,7 @@ class Command(BaseCommand): Usage: python manage.py load_state_data ./data/india/states-and-districts.json """ - help = "Loads Local Body data from a folder of JSONs" + help = "Loads Local Body data from a folder of JSONs" # noqa: A003 BASE_URL = "data/india/" valid_states = [ "andaman_and_nicobar_islands", @@ -58,12 +58,14 @@ def handle(self, *args, **options): states = self.valid_states else: if state not in self.valid_states: - print("valid state options are ", self.valid_states) + self.stdout.write( + self.style.ERROR(f"valid state options are {self.valid_states}"), + ) raise Exception("State not found") states = [state] for state in states: current_state_data = self.BASE_URL + state + "/lsg/" - print("Processing Files From", current_state_data) + self.stdout.write(f"Loading State Data {current_state_data}") management.call_command("load_lsg_data", current_state_data) management.call_command("load_ward_data", current_state_data) diff --git a/care/users/management/commands/load_lsg_data.py b/care/users/management/commands/load_lsg_data.py index fc70815973..e92c9284bf 100644 --- a/care/users/management/commands/load_lsg_data.py +++ b/care/users/management/commands/load_lsg_data.py @@ -1,7 +1,6 @@ import glob import json from collections import defaultdict -from typing import Optional from django.core.management.base import BaseCommand, CommandParser @@ -14,18 +13,18 @@ class Command(BaseCommand): Sample data: https://github.com/rebuildearth/data/tree/master/data/india/kerala/lsgi_site_data """ - help = "Loads Local Body data from a folder of JSONs" + help = "Loads Local Body data from a folder of JSONs" # noqa: A003 def add_arguments(self, parser: CommandParser) -> None: parser.add_argument("folder", help="path to the folder of JSONs") - def handle(self, *args, **options) -> Optional[str]: + def handle(self, *args, **options) -> str | None: folder = options["folder"] counter = 0 local_bodies = [] # Creates a map with first char of readable value as key - LOCAL_BODY_CHOICE_MAP = dict([(c[1][0], c[0]) for c in LOCAL_BODY_CHOICES]) + local_body_choice_map = dict([(c[1][0], c[0]) for c in LOCAL_BODY_CHOICES]) state = {} district = defaultdict(dict) @@ -35,7 +34,8 @@ def get_state_obj(state_name): return state[state_name] state_obj = State.objects.filter(name=state_name).first() if not state_obj: - print(f"Creating State {state_name}") + self.stdout.write(f"Creating State {state_name}") + self.stdout.write(f"Creating State {state_name}") state_obj = State(name=state_name) state_obj.save() state[state_name] = state_obj @@ -43,16 +43,16 @@ def get_state_obj(state_name): def get_district_obj(district_name, state_name): state_obj = get_state_obj(state_name) - if state_name in district: - if district_name in district[state_name]: - return district[state_name][district_name] + if state_name in district and district_name in district[state_name]: + return district[state_name][district_name] district_obj = District.objects.filter( - name=district_name, state=state_obj + name=district_name, + state=state_obj, ).first() if not district_obj: if not district_name: return None - print(f"Creating District {district_name}") + self.stdout.write(f"Creating District {district_name}") district_obj = District(name=district_name, state=state_obj) district_obj.save() district[state_name][district_name] = district_obj @@ -75,11 +75,11 @@ def create_local_bodies(local_body_list): name=lb["name"], district=dist_obj, localbody_code=lb.get("localbody_code"), - body_type=LOCAL_BODY_CHOICE_MAP.get( + body_type=local_body_choice_map.get( (lb.get("localbody_code", " "))[0], LOCAL_BODY_CHOICES[-1][0], ), - ) + ), ) # Possible conflict is name uniqueness. @@ -89,7 +89,7 @@ def create_local_bodies(local_body_list): for f in glob.glob(f"{folder}/*.json"): counter += 1 - with open(f"{f}", "r") as data_f: + with open(f"{f}") as data_f: data = json.load(data_f) data.pop("wards", None) local_bodies.append(data) @@ -98,7 +98,7 @@ def create_local_bodies(local_body_list): if counter % 1000 == 0: create_local_bodies(local_bodies) local_bodies = [] - print(f"Completed: {counter}") + self.stdout.write(f"Completed: {counter}") if len(local_bodies) > 0: create_local_bodies(local_bodies) diff --git a/care/users/management/commands/load_state_data.py b/care/users/management/commands/load_state_data.py index d72d28b022..9c65fa7e73 100644 --- a/care/users/management/commands/load_state_data.py +++ b/care/users/management/commands/load_state_data.py @@ -13,7 +13,7 @@ class Command(BaseCommand): Usage: python manage.py load_state_data ./data/india/states-and-districts.json """ - help = "Loads Local Body data from a folder of JSONs" + help = "Loads Local Body data from a folder of JSONs" # noqa: A003 def add_arguments(self, parser: CommandParser) -> None: parser.add_argument("json_file_path", help="path to the folder of JSONs") @@ -22,24 +22,31 @@ def handle(self, *args, **options): json_file_path = options["json_file_path"] data = [] - with open(json_file_path, "r") as json_file: + with open(json_file_path) as json_file: data = json.load(json_file) for item in data: state_name = item["state"].strip() if state_name.lower() in states_to_ignore: - print(f"Skipping {state_name}") + self.stdout.write(self.style.WARNING(f"Skipping {state_name}")) continue districts = [d.strip() for d in item["districts"].split(",")] state, is_created = State.objects.get_or_create( - name__iexact=state_name, defaults={"name": state_name} + name__iexact=state_name, + defaults={"name": state_name}, + ) + self.stdout.write( + f"{'Created' if is_created else 'Retrieved'} state {state_name}", ) - print(f"{'Created' if is_created else 'Retrieved'} {state_name}") for d in districts: _, is_created = District.objects.get_or_create( - state=state, name__iexact=d, defaults={"name": d} + state=state, + name__iexact=d, + defaults={"name": d}, + ) + self.stdout.write( + f"{'Created' if is_created else 'Retrieved'} district {d}", ) - print(f"{'Created' if is_created else 'Retrieved'} {state_name}") diff --git a/care/users/management/commands/load_ward_data.py b/care/users/management/commands/load_ward_data.py index 99c612851f..668183c749 100644 --- a/care/users/management/commands/load_ward_data.py +++ b/care/users/management/commands/load_ward_data.py @@ -1,6 +1,5 @@ import glob import json -from typing import Optional from django.core.management.base import BaseCommand, CommandParser from django.db import IntegrityError @@ -14,12 +13,12 @@ class Command(BaseCommand): Sample data: https://github.com/rebuildearth/data/tree/master/data/india/kerala/lsgi_site_data """ - help = "Loads Local Body data from a folder of JSONs" + help = "Loads Local Body data from a folder of JSONs" # noqa: A003 def add_arguments(self, parser: CommandParser) -> None: parser.add_argument("folder", help="path to the folder of JSONs") - def handle(self, *args, **options) -> Optional[str]: + def handle(self, *args, **options) -> str | None: def int_or_zero(value): try: int(value) @@ -44,7 +43,7 @@ def get_ward_name(ward): district_map = {d.name: d for d in districts} # Creates a map with first char of readable value as key - LOCAL_BODY_CHOICE_MAP = dict([(c[1][0], c[0]) for c in LOCAL_BODY_CHOICES]) + local_body_choice_map = dict([(c[1][0], c[0]) for c in LOCAL_BODY_CHOICES]) def get_local_body(lb): if not lb["district"]: @@ -53,18 +52,18 @@ def get_local_body(lb): name=lb["name"], district=district_map[lb["district"]], localbody_code=lb.get("localbody_code"), - body_type=LOCAL_BODY_CHOICE_MAP.get( + body_type=local_body_choice_map.get( (lb.get("localbody_code", " "))[0], LOCAL_BODY_CHOICES[-1][0], ), ).first() for f in glob.glob(f"{folder}/*.json"): - with open(f"{f}", "r") as data_f: + with open(f"{f}") as data_f: data = json.load(data_f) wards = data.pop("wards", None) if wards is None: - print("Ward Data not Found ") + self.stdout.write(self.style.WARNING(f"Ward Data not Found {f}")) if data.get("district") is not None: local_body = get_local_body(data) if not local_body: @@ -80,4 +79,4 @@ def get_local_body(lb): obj.save() except IntegrityError: pass - print("Processed ", str(counter), " wards") + self.stdout.write(self.style.SUCCESS(f"Processed {str(counter)} wards")) diff --git a/care/users/management/commands/populate_investigations.py b/care/users/management/commands/populate_investigations.py index 1603041f3b..c22b63203b 100644 --- a/care/users/management/commands/populate_investigations.py +++ b/care/users/management/commands/populate_investigations.py @@ -158,7 +158,7 @@ class Command(BaseCommand): Populates Investigation seed data """ - help = "Seed Data for Investigations" + help = "Seed Data for Investigations" # noqa: A003 def handle(self, *args, **options): investigation_group_data = investigation_groups.split("\n")[1:] @@ -166,11 +166,11 @@ def handle(self, *args, **options): for investigation_group in investigation_group_data: current_investigation_group = investigation_group.split("\t") current_obj = PatientInvestigationGroup.objects.filter( - name=current_investigation_group[1] + name=current_investigation_group[1], ).first() if not current_obj: current_obj = PatientInvestigationGroup( - name=current_investigation_group[1] + name=current_investigation_group[1], ) current_obj.save() investigation_group_dict[current_investigation_group[0]] = current_obj diff --git a/care/users/management/commands/seed_data.py b/care/users/management/commands/seed_data.py index 0229caca9e..4cc0be8366 100644 --- a/care/users/management/commands/seed_data.py +++ b/care/users/management/commands/seed_data.py @@ -13,11 +13,12 @@ class Command(BaseCommand): Command to add data related to inventory and their conversion rates """ - help = "Seed Data for Inventory" + help = "Seed Data for Inventory" # noqa: A003 def handle(self, *args, **options): - print("Creating Units for Inventory as well as their conversion rates") - + self.stdout.write( + "Creating Units for Inventory as well as their conversion rates", + ) # Inventory Unit items, _ = FacilityInventoryUnit.objects.get_or_create(name="Items") @@ -37,19 +38,25 @@ def handle(self, *args, **options): # Inventory Item ppe, _ = FacilityInventoryItem.objects.get_or_create( - name="PPE", default_unit=items, min_quantity=150 + name="PPE", + default_unit=items, + min_quantity=150, ) ppe.tags.add(safety, medical) ppe.allowed_units.add(items, dozen) fluid, _ = FacilityInventoryItem.objects.get_or_create( - name="IV Fluid 500 ml", default_unit=items, min_quantity=2 + name="IV Fluid 500 ml", + default_unit=items, + min_quantity=2, ) fluid.tags.add(medical) fluid.allowed_units.add(items, dozen) liquid_oxygen, _ = FacilityInventoryItem.objects.get_or_create( - name="Liquid Oxygen", default_unit=cubic_meter, min_quantity=10 + name="Liquid Oxygen", + default_unit=cubic_meter, + min_quantity=10, ) liquid_oxygen.tags.add(medical) liquid_oxygen.allowed_units.add(cubic_meter) @@ -62,17 +69,23 @@ def handle(self, *args, **options): jumbo_d.allowed_units.add(cylinders) type_b, _ = FacilityInventoryItem.objects.get_or_create( - name="B Type Oxygen Cylinder", default_unit=cylinders, min_quantity=100 + name="B Type Oxygen Cylinder", + default_unit=cylinders, + min_quantity=100, ) type_b.allowed_units.add(cylinders) type_c, _ = FacilityInventoryItem.objects.get_or_create( - name="C Type Oxygen Cylinder", default_unit=cylinders, min_quantity=100 + name="C Type Oxygen Cylinder", + default_unit=cylinders, + min_quantity=100, ) type_c.allowed_units.add(cylinders) gaseous_oxygen, _ = FacilityInventoryItem.objects.get_or_create( - name="Gaseous Oxygen", default_unit=cubic_meter, min_quantity=10 + name="Gaseous Oxygen", + default_unit=cubic_meter, + min_quantity=10, ) gaseous_oxygen.tags.add(medical) gaseous_oxygen.allowed_units.add(cubic_meter) @@ -80,9 +93,13 @@ def handle(self, *args, **options): # Conversion Rates _, _ = FacilityInventoryUnitConverter.objects.get_or_create( - from_unit=kg, to_unit=gram, multiplier=1000 + from_unit=kg, + to_unit=gram, + multiplier=1000, ) _, _ = FacilityInventoryUnitConverter.objects.get_or_create( - from_unit=dozen, to_unit=items, multiplier=12 + from_unit=dozen, + to_unit=items, + multiplier=12, ) diff --git a/care/users/models.py b/care/users/models.py index 28ec87ed9e..287ac81a46 100644 --- a/care/users/models.py +++ b/care/users/models.py @@ -123,7 +123,9 @@ class CustomUserManager(UserManager): def get_queryset(self): qs = super().get_queryset() return qs.filter(deleted=False, is_active=True).select_related( - "local_body", "district", "state" + "local_body", + "district", + "state", ) def get_entire_queryset(self): @@ -150,7 +152,7 @@ def __str__(self): class UsernameValidator(UnicodeUsernameValidator): regex = r"^[\w.@+-]+[^.@+_-]$" message = _( - "Please enter letters, digits and @ . + - _ only and username should not end with @ . + - or _" + "Please enter letters, digits and @ . + - _ only and username should not end with @ . + - or _", ) @@ -164,7 +166,7 @@ class Meta: fields=["skill", "user"], condition=models.Q(deleted=False), name="unique_user_skill", - ) + ), ] @@ -213,15 +215,22 @@ class User(AbstractUser): ward = models.ForeignKey(Ward, on_delete=models.PROTECT, null=True, blank=True) local_body = models.ForeignKey( - LocalBody, on_delete=models.PROTECT, null=True, blank=True + LocalBody, + on_delete=models.PROTECT, + null=True, + blank=True, ) district = models.ForeignKey( - District, on_delete=models.PROTECT, null=True, blank=True + District, + on_delete=models.PROTECT, + null=True, + blank=True, ) state = models.ForeignKey(State, on_delete=models.PROTECT, null=True, blank=True) phone_number = models.CharField( - max_length=14, validators=[mobile_or_landline_number_validator] + max_length=14, + validators=[mobile_or_landline_number_validator], ) alt_phone_number = models.CharField( max_length=14, @@ -235,10 +244,15 @@ class User(AbstractUser): age = models.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(100)]) skills = models.ManyToManyField("Skill", through=UserSkill) home_facility = models.ForeignKey( - "facility.Facility", on_delete=models.PROTECT, null=True, blank=True + "facility.Facility", + on_delete=models.PROTECT, + null=True, + blank=True, ) weekly_working_hours = models.IntegerField( - validators=[MinValueValidator(0), MaxValueValidator(168)], null=True, blank=True + validators=[MinValueValidator(0), MaxValueValidator(168)], + null=True, + blank=True, ) doctor_qualification = models.TextField( @@ -369,7 +383,12 @@ class UserFacilityAllocation(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="+") facility = models.ForeignKey( - "facility.Facility", on_delete=models.CASCADE, related_name="+" + "facility.Facility", + on_delete=models.CASCADE, + related_name="+", ) start_date = models.DateTimeField(default=now) end_date = models.DateTimeField(null=True, blank=True) + + def __str__(self) -> str: + return f"{self.user} - {self.facility}" diff --git a/care/users/reset_password_views.py b/care/users/reset_password_views.py index 6cec930e4c..0f723128f1 100644 --- a/care/users/reset_password_views.py +++ b/care/users/reset_password_views.py @@ -31,10 +31,14 @@ User = get_user_model() HTTP_USER_AGENT_HEADER = getattr( - settings, "DJANGO_REST_PASSWORDRESET_HTTP_USER_AGENT_HEADER", "HTTP_USER_AGENT" + settings, + "DJANGO_REST_PASSWORDRESET_HTTP_USER_AGENT_HEADER", + "HTTP_USER_AGENT", ) HTTP_IP_ADDRESS_HEADER = getattr( - settings, "DJANGO_REST_PASSWORDRESET_IP_ADDRESS_HEADER", "REMOTE_ADDR" + settings, + "DJANGO_REST_PASSWORDRESET_IP_ADDRESS_HEADER", + "REMOTE_ADDR", ) @@ -73,7 +77,7 @@ def post(self, request, *args, **kwargs): # check expiry date expiry_date = reset_password_token.created_at + timedelta( - hours=password_reset_token_validation_time + hours=password_reset_token_validation_time, ) if timezone.now() > expiry_date: @@ -122,7 +126,7 @@ def post(self, request, *args, **kwargs): # check expiry date expiry_date = reset_password_token.created_at + timedelta( - hours=password_reset_token_validation_time + hours=password_reset_token_validation_time, ) if timezone.now() > expiry_date: @@ -136,7 +140,8 @@ def post(self, request, *args, **kwargs): # change users password (if we got to this code it means that the user is_active) if reset_password_token.user.eligible_for_reset(): pre_password_reset.send( - sender=self.__class__, user=reset_password_token.user + sender=self.__class__, + user=reset_password_token.user, ) try: # validate the password against existing validators @@ -144,17 +149,18 @@ def post(self, request, *args, **kwargs): password, user=reset_password_token.user, password_validators=get_password_validators( - settings.AUTH_PASSWORD_VALIDATORS + settings.AUTH_PASSWORD_VALIDATORS, ), ) except ValidationError as e: # raise a validation error for the serializer - raise exceptions.ValidationError({"password": e.messages}) + raise exceptions.ValidationError({"password": e.messages}) from e reset_password_token.user.set_password(password) reset_password_token.user.save() post_password_reset.send( - sender=self.__class__, user=reset_password_token.user + sender=self.__class__, + user=reset_password_token.user, ) # Delete all password reset tokens for this user @@ -191,7 +197,7 @@ def post(self, request, *args, **kwargs): # datetime.now minus expiry hours now_minus_expiry_time = timezone.now() - timedelta( - hours=password_reset_token_validation_time + hours=password_reset_token_validation_time, ) # delete all tokens where created_at < now - 24 hours @@ -199,7 +205,7 @@ def post(self, request, *args, **kwargs): # find a user users = User.objects.filter( - **{"{}__exact".format(get_password_reset_lookup_field()): username} + **{f"{get_password_reset_lookup_field()}__exact": username}, ) active_user_found = False @@ -214,16 +220,18 @@ def post(self, request, *args, **kwargs): # No active user found, raise a validation error # but not if DJANGO_REST_PASSWORDRESET_NO_INFORMATION_LEAKAGE == True if not active_user_found and not getattr( - settings, "DJANGO_REST_PASSWORDRESET_NO_INFORMATION_LEAKAGE", False + settings, + "DJANGO_REST_PASSWORDRESET_NO_INFORMATION_LEAKAGE", + False, ): raise exceptions.ValidationError( { "email": [ _( - "There is no active user associated with this e-mail address or the password can not be changed" - ) + "There is no active user associated with this e-mail address or the password can not be changed", + ), ], - } + }, ) # last but not least: iterate over all users that are active and can change their password @@ -247,7 +255,9 @@ def post(self, request, *args, **kwargs): # send a signal that the password token was created # let whoever receives this signal handle sending the email for the password reset reset_password_token_created.send( - sender=self.__class__, instance=self, reset_password_token=token + sender=self.__class__, + instance=self, + reset_password_token=token, ) # done return Response({"status": "OK"}) diff --git a/care/users/signals.py b/care/users/signals.py index 85feb06a19..5047a78cb0 100644 --- a/care/users/signals.py +++ b/care/users/signals.py @@ -13,7 +13,11 @@ @receiver(reset_password_token_created) def password_reset_token_created( - sender, instance, reset_password_token, *args, **kwargs + sender, + instance, + reset_password_token, + *args, + **kwargs, ): """ Handles password reset tokens @@ -31,7 +35,8 @@ def password_reset_token_created( "username": reset_password_token.user.username, "email": reset_password_token.user.email, "reset_password_url": "{}/password_reset/{}".format( - settings.CURRENT_DOMAIN, reset_password_token.key + settings.CURRENT_DOMAIN, + reset_password_token.key, ), } @@ -60,20 +65,20 @@ def save_fields_before_update(sender, instance, raw, using, update_fields, **kwa if fields_to_save: with contextlib.suppress(IndexError): instance._previous_values = instance.__class__._base_manager.filter( - pk=instance.pk + pk=instance.pk, ).values(*fields_to_save)[0] @receiver(post_save, sender=settings.AUTH_USER_MODEL) def track_user_facility_allocation( - sender, instance, created, raw, using, update_fields, **kwargs + sender, instance, created, raw, using, update_fields, **kwargs, ): if raw or (update_fields and "home_facility" not in update_fields): return if created and instance.home_facility: UserFacilityAllocation.objects.create( - user=instance, facility=instance.home_facility + user=instance, facility=instance.home_facility, ) return @@ -84,11 +89,11 @@ def track_user_facility_allocation( ) or instance.deleted: # this also includes the case when the user's new home facility is set to None UserFacilityAllocation.objects.filter( - user=instance, facility=last_home_facility, end_date__isnull=True + user=instance, facility=last_home_facility, end_date__isnull=True, ).update(end_date=now()) if instance.home_facility_id and instance.home_facility_id != last_home_facility: # create a new allocation if new home facility is changed UserFacilityAllocation.objects.create( - user=instance, facility=instance.home_facility + user=instance, facility=instance.home_facility, ) diff --git a/care/users/tests/test_api.py b/care/users/tests/test_api.py index 8718312db7..52812b3dfa 100644 --- a/care/users/tests/test_api.py +++ b/care/users/tests/test_api.py @@ -144,7 +144,7 @@ def test_user_can_read_all(self): def test_user_can_modify_themselves(self): """Test user can modify the attributes for themselves""" - password = "new_password" + password = "new_password" # noqa: S105 username = self.user.username response = self.client.patch( f"/api/v1/users/{username}/", diff --git a/care/users/tests/test_auth.py b/care/users/tests/test_auth.py index 29e5f4168c..4cb551a43f 100644 --- a/care/users/tests/test_auth.py +++ b/care/users/tests/test_auth.py @@ -110,7 +110,9 @@ def setUpTestData(cls) -> None: cls.user = cls.create_user("user", cls.district) def create_reset_password_token( - self, user: User, expired: bool = False + self, + user: User, + expired: bool = False, # noqa: FBT001 ) -> ResetPasswordToken: token = ResetPasswordToken.objects.create(user=user) if expired: diff --git a/care/users/tests/test_models.py b/care/users/tests/test_models.py index a559df17e4..40a7df2e7e 100644 --- a/care/users/tests/test_models.py +++ b/care/users/tests/test_models.py @@ -10,7 +10,8 @@ def setUpTestData(cls): Initialise test data for all other methods """ cls.skill = Skill.objects.create( - name="ghatak", description="corona virus specialist" + name="ghatak", + description="corona virus specialist", ) def test_max_length_name(self): @@ -78,7 +79,9 @@ def setUpTestData(cls): state = State.objects.create(name="bihar") district = District.objects.create(state=state, name="nam") cls.local_body = LocalBody.objects.create( - district=district, name="blabla", body_type=1 + district=district, + name="blabla", + body_type=1, ) def test_max_length_name(self): diff --git a/care/users/tests/test_user_homefacility_allocation_tracking.py b/care/users/tests/test_user_homefacility_allocation_tracking.py index 726e562182..ba3c094fd5 100644 --- a/care/users/tests/test_user_homefacility_allocation_tracking.py +++ b/care/users/tests/test_user_homefacility_allocation_tracking.py @@ -34,7 +34,7 @@ def test_user_facility_allocation_is_ended_when_home_facility_is_cleared(self): user.home_facility = None user.save() allocation = UserFacilityAllocation.objects.get( - user=user, facility=self.facility + user=user, facility=self.facility, ) self.assertIsNotNone(allocation.end_date) @@ -47,7 +47,7 @@ def test_user_facility_allocation_is_ended_when_user_is_deleted(self): user.deleted = True user.save() allocation = UserFacilityAllocation.objects.get( - user=user, facility=self.facility + user=user, facility=self.facility, ) self.assertIsNotNone(allocation.end_date) @@ -58,25 +58,25 @@ def test_user_facility_allocation_on_home_facility_changed(self): home_facility=self.facility, ) new_facility = self.create_facility( - self.super_user, self.district, self.local_body + self.super_user, self.district, self.local_body, ) user.home_facility = new_facility user.save() allocation = UserFacilityAllocation.objects.get( - user=user, facility=self.facility + user=user, facility=self.facility, ) self.assertIsNotNone(allocation.end_date) self.assertTrue( UserFacilityAllocation.objects.filter( - user=user, facility=new_facility - ).exists() + user=user, facility=new_facility, + ).exists(), ) def test_user_facility_allocation_is_not_created_when_user_is_created_without_home_facility( self, ): user = self.create_user( - district=self.district, username="facility_allocation_test_user" + district=self.district, username="facility_allocation_test_user", ) self.assertFalse(UserFacilityAllocation.objects.filter(user=user).exists()) diff --git a/care/utils/assetintegration/base.py b/care/utils/assetintegration/base.py index 92d318c3a5..ff455e059c 100644 --- a/care/utils/assetintegration/base.py +++ b/care/utils/assetintegration/base.py @@ -1,4 +1,5 @@ import json +import logging import requests from django.conf import settings @@ -6,6 +7,10 @@ from care.utils.jwks.token_generator import generate_jwt +logger = logging.getLogger(__name__) + +MIDDLEWARE_REQUEST_TIMEOUT = 25 + class BaseAssetIntegration: auth_header_type = "Care_Bearer " @@ -26,29 +31,33 @@ def get_url(self, endpoint): return f"{protocol}://{self.middleware_hostname}/{endpoint}" def api_post(self, url, data=None): - req = requests.post( - url, - json=data, - headers={"Authorization": (self.auth_header_type + generate_jwt())}, - ) try: - response = req.json() - if req.status_code >= 400: - raise APIException(response, req.status_code) - return response + response = requests.post( + url, + json=data, + headers={"Authorization": (self.auth_header_type + generate_jwt())}, + timeout=MIDDLEWARE_REQUEST_TIMEOUT, + ) + response.raise_for_status() + return response.json() + except requests.exceptions.HTTPError as e: + logger.error("middleware error: %s", e.errno) + raise APIException({"error": "error occurred on middleware"}) from None except json.decoder.JSONDecodeError: - return {"error": "Invalid Response"} + raise APIException({"error": "Invalid Response"}) from None def api_get(self, url, data=None): - req = requests.get( - url, - params=data, - headers={"Authorization": (self.auth_header_type + generate_jwt())}, - ) try: - if req.status_code >= 400: - raise APIException(req.text, req.status_code) - response = req.json() - return response + response = requests.get( + url, + params=data, + headers={"Authorization": (self.auth_header_type + generate_jwt())}, + timeout=MIDDLEWARE_REQUEST_TIMEOUT, + ) + response.raise_for_status() + return response.json() + except requests.exceptions.HTTPError as e: + logger.error("middleware error: %s", e.errno) + raise APIException({"error": "error occurred on middleware"}) from None except json.decoder.JSONDecodeError: - return {"error": "Invalid Response"} + raise APIException({"error": "Invalid Response"}) from None diff --git a/care/utils/assetintegration/hl7monitor.py b/care/utils/assetintegration/hl7monitor.py index f7f1e7c783..3647fd4f85 100644 --- a/care/utils/assetintegration/hl7monitor.py +++ b/care/utils/assetintegration/hl7monitor.py @@ -16,8 +16,8 @@ def __init__(self, meta): super().__init__(meta) except KeyError as e: raise ValidationError( - dict((key, f"{key} not found in asset metadata") for key in e.args) - ) + dict((key, f"{key} not found in asset metadata") for key in e.args), + ) from None def handle_action(self, action): action_type = action["type"] diff --git a/care/utils/assetintegration/onvif.py b/care/utils/assetintegration/onvif.py index 8e21c77d43..c31653b98a 100644 --- a/care/utils/assetintegration/onvif.py +++ b/care/utils/assetintegration/onvif.py @@ -23,8 +23,8 @@ def __init__(self, meta): self.access_key = self.meta["camera_access_key"].split(":")[2] except KeyError as e: raise ValidationError( - dict((key, f"{key} not found in asset metadata") for key in e.args) - ) + dict((key, f"{key} not found in asset metadata") for key in e.args), + ) from None def handle_action(self, action): action_type = action["type"] diff --git a/care/utils/assetintegration/ventilator.py b/care/utils/assetintegration/ventilator.py index 10af39b50f..3f9f921918 100644 --- a/care/utils/assetintegration/ventilator.py +++ b/care/utils/assetintegration/ventilator.py @@ -16,8 +16,8 @@ def __init__(self, meta): super().__init__(meta) except KeyError as e: raise ValidationError( - dict((key, f"{key} not found in asset metadata") for key in e.args) - ) + dict((key, f"{key} not found in asset metadata") for key in e.args), + ) from None def handle_action(self, action): action_type = action["type"] diff --git a/care/utils/cache/__init__.py b/care/utils/cache/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/care/utils/cache/cache_allowed_facilities.py b/care/utils/cache/cache_allowed_facilities.py index a53d226bf6..a6ededce79 100644 --- a/care/utils/cache/cache_allowed_facilities.py +++ b/care/utils/cache/cache_allowed_facilities.py @@ -10,8 +10,9 @@ def get_accessible_facilities(user): if not hit: facility_ids = list( FacilityUser.objects.filter(user_id=user_id).values_list( - "facility__id", flat=True - ) + "facility__id", + flat=True, + ), ) cache.set(key, facility_ids) return facility_ids diff --git a/care/utils/cache/patient_investigation.py b/care/utils/cache/patient_investigation.py index 606f1e4791..c06fd825b1 100644 --- a/care/utils/cache/patient_investigation.py +++ b/care/utils/cache/patient_investigation.py @@ -8,7 +8,7 @@ def get_investigation_id(investigation_external_id): hit = cache.get(key) if not hit: investigation_id = PatientInvestigation.objects.get( - external_id=investigation_external_id + external_id=investigation_external_id, ).id cache.set(key, investigation_id) return investigation_id diff --git a/care/utils/csp/__init__.py b/care/utils/csp/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/care/utils/csp/config.py b/care/utils/csp/config.py index 0f6673c3ad..4d265a1b4d 100644 --- a/care/utils/csp/config.py +++ b/care/utils/csp/config.py @@ -33,8 +33,8 @@ def get_client_config(bucket_type=BucketType.PATIENT.value): }, } - if settings.CLOUD_PROVIDER == CSProvider.GCP.value: - for key in config.keys(): + if CSProvider.GCP.value == settings.CLOUD_PROVIDER: + for key in config: config[key]["endpoint_url"] = "https://storage.googleapis.com" return config[bucket_type] diff --git a/care/utils/exceptions.py b/care/utils/exceptions.py index de8c19ad8f..e19f42241f 100644 --- a/care/utils/exceptions.py +++ b/care/utils/exceptions.py @@ -1,2 +1,13 @@ -class CeleryTaskException(Exception): +from django.utils.translation import gettext_lazy as _ +from rest_framework import status +from rest_framework_simplejwt.exceptions import AuthenticationFailed + + +class CeleryTaskError(Exception): pass + + +class CaptchaRequiredException(AuthenticationFailed): + status_code = status.HTTP_429_TOO_MANY_REQUESTS + default_detail = _("Too Many Requests Provide Captcha") + default_code = "captchaRequired" diff --git a/care/utils/filters/multiselect.py b/care/utils/filters/multiselect.py index 30c91bc446..d9c7ede2aa 100644 --- a/care/utils/filters/multiselect.py +++ b/care/utils/filters/multiselect.py @@ -2,12 +2,11 @@ class MultiSelectFilter(Filter): - def filter(self, qs, value): + def filter(self, qs, value): # noqa: A003 if not value: return qs if not self.field_name: - return + return None values_list = value.split(",") filters = {self.field_name + "__in": values_list} - qs = qs.filter(**filters) - return qs + return qs.filter(**filters) diff --git a/care/utils/jwks/token_generator.py b/care/utils/jwks/token_generator.py index fe06421fb3..fc4c874cac 100644 --- a/care/utils/jwks/token_generator.py +++ b/care/utils/jwks/token_generator.py @@ -1,14 +1,13 @@ -from datetime import datetime - from authlib.jose import jwt from django.conf import settings +from django.utils import timezone def generate_jwt(claims=None, exp=60): if claims is None: claims = {} header = {"alg": "RS256"} - time = int(datetime.now().timestamp()) + time = int(timezone.now().timestamp()) payload = { "iat": time, "exp": time + exp, diff --git a/care/utils/models/base.py b/care/utils/models/base.py index 6e61afb567..4b6a6217d3 100644 --- a/care/utils/models/base.py +++ b/care/utils/models/base.py @@ -18,10 +18,16 @@ def get_queryset(self): class BaseModel(models.Model): external_id = models.UUIDField(default=uuid4, unique=True, db_index=True) created_date = models.DateTimeField( - auto_now_add=True, null=True, blank=True, db_index=True + auto_now_add=True, + null=True, + blank=True, + db_index=True, ) modified_date = models.DateTimeField( - auto_now=True, null=True, blank=True, db_index=True + auto_now=True, + null=True, + blank=True, + db_index=True, ) deleted = models.BooleanField(default=False, db_index=True) diff --git a/care/utils/models/validators.py b/care/utils/models/validators.py index e9c8901ea0..cf8acd06f1 100644 --- a/care/utils/models/validators.py +++ b/care/utils/models/validators.py @@ -1,4 +1,4 @@ -from typing import Iterable, List +from collections.abc import Iterable import jsonschema from django.core.exceptions import ValidationError @@ -34,7 +34,7 @@ def __eq__(self, other): def _extract_errors( self, errors: Iterable[jsonschema.ValidationError], - container: List[ValidationError], + container: list[ValidationError], ): for error in errors: if error.context: @@ -42,6 +42,7 @@ def _extract_errors( message = str(error).replace("\n\n", ": ").replace("\n", "") container.append(ValidationError(message)) + return None @deconstructible @@ -86,7 +87,7 @@ def __init__(self, types: Iterable[str], *args, **kwargs): self.message = f"Invalid phone number. Must be one of the following types: {', '.join(self.types)}. Received: %(value)s" self.code = "invalid_phone_number" - self.regex = r"|".join([self.regex_map[type] for type in self.types]) + self.regex = r"|".join([self.regex_map[_type] for _type in self.types]) super().__init__(*args, **kwargs) def __eq__(self, other): diff --git a/care/utils/notification_handler.py b/care/utils/notification_handler.py index 677eb132d4..2e9f14d985 100644 --- a/care/utils/notification_handler.py +++ b/care/utils/notification_handler.py @@ -1,4 +1,5 @@ import json +import logging from celery import shared_task from django.apps import apps @@ -16,10 +17,12 @@ ) from care.facility.models.shifting import ShiftingRequest from care.users.models import User -from care.utils.sms.sendSMS import sendSMS +from care.utils import sms +logger = logging.getLogger(__name__) -class NotificationCreationException(Exception): + +class NotificationCreationError(Exception): pass @@ -37,8 +40,8 @@ def send_webpush(**kwargs): def get_model_class(model_name): if model_name == "User": - return apps.get_model("users.{}".format(model_name)) - return apps.get_model("facility.{}".format(model_name)) + return apps.get_model(f"users.{model_name}") + return apps.get_model(f"facility.{model_name}") class NotificationGenerator: @@ -46,7 +49,7 @@ class NotificationGenerator: generate_for_user = False facility = None - def __init__( + def __init__( # noqa: PLR0913 self, event_type=Notification.EventType.SYSTEM_GENERATED, event=None, @@ -64,23 +67,22 @@ def __init__( ): if not worker_initated: if not isinstance(event_type, Notification.EventType): - raise NotificationCreationException("Event Type Invalid") + raise NotificationCreationError("Event Type Invalid") if not isinstance(event, Notification.Event): - raise NotificationCreationException("Event Invalid") + raise NotificationCreationError("Event Invalid") if not isinstance(caused_by, User): - raise NotificationCreationException( - "edited_by must be an instance of a user" + raise NotificationCreationError( + "edited_by must be an instance of a user", + ) + if facility and not isinstance(facility, Facility): + raise NotificationCreationError( + "facility must be an instance of Facility", ) - if facility: - if not isinstance(facility, Facility): - raise NotificationCreationException( - "facility must be an instance of Facility" - ) mediums = [] if notification_mediums: for medium in notification_mediums: if not isinstance(medium, Notification.Medium): - raise NotificationCreationException("Medium Type Invalid") + raise NotificationCreationError("Medium Type Invalid") mediums.append(medium.value) data = { "event_type": event_type.value, @@ -102,8 +104,7 @@ def __init__( self.worker_initiated = False return self.worker_initiated = True - Model = get_model_class(caused_object) - caused_object = Model.objects.get(id=caused_object_pk) + caused_object = get_model_class(caused_object).objects.get(id=caused_object_pk) caused_by = User.objects.get(id=caused_by) facility = Facility.objects.get(id=facility) self.notification_mediums = notification_mediums @@ -138,57 +139,73 @@ def deserialize_extra_data(self, extra_data): return None for key in extra_data: extra_data[key] = apps.get_model( - "facility.{}".format(extra_data[key]["model_name"]) + "facility.{}".format(extra_data[key]["model_name"]), ).objects.get(id=extra_data[key]["model_id"]) return extra_data def generate_extra_users(self): - if isinstance(self.caused_object, PatientConsultation): - if self.caused_object.assigned_to: - self.extra_users.append(self.caused_object.assigned_to.id) - if isinstance(self.caused_object, PatientRegistration): - if self.caused_object.last_consultation: - if self.caused_object.last_consultation.assigned_to: - self.extra_users.append( - self.caused_object.last_consultation.assigned_to.id - ) - if isinstance(self.caused_object, InvestigationSession): - if self.extra_data["consultation"].assigned_to: - self.extra_users.append(self.extra_data["consultation"].assigned_to.id) - if isinstance(self.caused_object, InvestigationValue): - if self.caused_object.consultation.assigned_to: - self.extra_users.append(self.caused_object.consultation.assigned_to.id) - if isinstance(self.caused_object, DailyRound): - if self.caused_object.consultation.assigned_to: - self.extra_users.append(self.caused_object.consultation.assigned_to.id) + if ( + isinstance(self.caused_object, PatientConsultation) + and self.caused_object.assigned_to + ): + self.extra_users.append(self.caused_object.assigned_to.id) + if ( + isinstance(self.caused_object, PatientRegistration) + and self.caused_object.last_consultation + and self.caused_object.last_consultation.assigned_to + ): + self.extra_users.append( + self.caused_object.last_consultation.assigned_to.id, + ) + if ( + isinstance(self.caused_object, InvestigationSession) + and self.extra_data["consultation"].assigned_to + ): + self.extra_users.append(self.extra_data["consultation"].assigned_to.id) + if ( + isinstance(self.caused_object, InvestigationValue) + and self.caused_object.consultation.assigned_to + ): + self.extra_users.append(self.caused_object.consultation.assigned_to.id) + if ( + isinstance(self.caused_object, DailyRound) + and self.caused_object.consultation.assigned_to + ): + self.extra_users.append(self.caused_object.consultation.assigned_to.id) - def generate_system_message(self): + def generate_system_message(self): # noqa: PLR0912 message = "" if isinstance(self.caused_object, PatientRegistration): if self.event == Notification.Event.PATIENT_CREATED.value: message = "Patient {} was created by {}".format( - self.caused_object.name, self.caused_by.get_full_name() + self.caused_object.name, + self.caused_by.get_full_name(), ) elif self.event == Notification.Event.PATIENT_UPDATED.value: message = "Patient {} was updated by {}".format( - self.caused_object.name, self.caused_by.get_full_name() + self.caused_object.name, + self.caused_by.get_full_name(), ) if self.event == Notification.Event.PATIENT_DELETED.value: message = "Patient {} was deleted by {}".format( - self.caused_object.name, self.caused_by.get_full_name() + self.caused_object.name, + self.caused_by.get_full_name(), ) elif isinstance(self.caused_object, PatientConsultation): if self.event == Notification.Event.PATIENT_CONSULTATION_CREATED.value: message = "Consultation for Patient {} was created by {}".format( - self.caused_object.patient.name, self.caused_by.get_full_name() + self.caused_object.patient.name, + self.caused_by.get_full_name(), ) elif self.event == Notification.Event.PATIENT_CONSULTATION_UPDATED.value: message = "Consultation for Patient {} was updated by {}".format( - self.caused_object.patient.name, self.caused_by.get_full_name() + self.caused_object.patient.name, + self.caused_by.get_full_name(), ) if self.event == Notification.Event.PATIENT_CONSULTATION_DELETED.value: message = "Consultation for Patient {} was deleted by {}".format( - self.caused_object.patient.name, self.caused_by.get_full_name() + self.caused_object.patient.name, + self.caused_by.get_full_name(), ) elif isinstance(self.caused_object, InvestigationSession): if self.event == Notification.Event.INVESTIGATION_SESSION_CREATED.value: @@ -224,22 +241,26 @@ def generate_system_message(self): self.caused_object.consultation.facility.name, self.caused_by.get_full_name(), ) - elif isinstance(self.caused_object, ShiftingRequest): - if self.event == Notification.Event.SHIFTING_UPDATED.value: - message = "Shifting for Patient {} was updated by {}".format( - self.caused_object.patient.name, - self.caused_by.get_full_name(), - ) + elif ( + isinstance(self.caused_object, ShiftingRequest) + and self.event == Notification.Event.SHIFTING_UPDATED.value + ): + message = "Shifting for Patient {} was updated by {}".format( + self.caused_object.patient.name, + self.caused_by.get_full_name(), + ) return message def generate_sms_message(self): message = "" - if isinstance(self.caused_object, ShiftingRequest): - if self.event == Notification.Event.SHIFTING_UPDATED.value: - message = "Your Shifting Request to {} has been approved in Care. Please contact {} for any queries".format( - self.caused_object.assigned_facility.name, - self.caused_object.shifting_approving_facility.phone_number, - ) + if ( + isinstance(self.caused_object, ShiftingRequest) + and self.event == Notification.Event.SHIFTING_UPDATED.value + ): + message = "Your Shifting Request to {} has been approved in Care. Please contact {} for any queries".format( + self.caused_object.assigned_facility.name, + self.caused_object.shifting_approving_facility.phone_number, + ) return message def generate_sms_phone_numbers(self): @@ -249,6 +270,7 @@ def generate_sms_phone_numbers(self): self.caused_object.patient.phone_number, self.caused_object.patient.emergency_phone_number, ] + return None def _get_default_medium(self): return [Notification.Medium.SYSTEM.value] @@ -258,53 +280,53 @@ def generate_cause_objects(self): self.caused_objects["patient"] = str(self.caused_object.external_id) if self.caused_object.facility: self.caused_objects["facility"] = str( - self.caused_object.facility.external_id + self.caused_object.facility.external_id, ) if isinstance(self.caused_object, PatientConsultation): self.caused_objects["consultation"] = str(self.caused_object.external_id) self.caused_objects["patient"] = str(self.caused_object.patient.external_id) if self.caused_object.patient.facility: self.caused_objects["facility"] = str( - self.caused_object.patient.facility.external_id + self.caused_object.patient.facility.external_id, ) if isinstance(self.caused_object, InvestigationSession): self.caused_objects["consultation"] = str( - self.extra_data["consultation"].external_id + self.extra_data["consultation"].external_id, ) self.caused_objects["patient"] = str( - self.extra_data["consultation"].patient.external_id + self.extra_data["consultation"].patient.external_id, ) if self.extra_data["consultation"].patient.facility: self.caused_objects["facility"] = str( - self.extra_data["consultation"].patient.facility.external_id + self.extra_data["consultation"].patient.facility.external_id, ) self.caused_objects["session"] = str(self.caused_object.external_id) if isinstance(self.caused_object, InvestigationValue): self.caused_objects["consultation"] = str( - self.caused_object.consultation.external_id + self.caused_object.consultation.external_id, ) self.caused_objects["patient"] = str( - self.caused_object.consultation.patient.external_id + self.caused_object.consultation.patient.external_id, ) if self.caused_object.consultation.patient.facility: self.caused_objects["facility"] = str( - self.caused_object.consultation.patient.facility.external_id + self.caused_object.consultation.patient.facility.external_id, ) self.caused_objects["session"] = str(self.caused_object.session.external_id) self.caused_objects["investigation"] = str( - self.caused_object.investigation.external_id + self.caused_object.investigation.external_id, ) if isinstance(self.caused_object, DailyRound): self.caused_objects["consultation"] = str( - self.caused_object.consultation.external_id + self.caused_object.consultation.external_id, ) self.caused_objects["patient"] = str( - self.caused_object.consultation.patient.external_id + self.caused_object.consultation.patient.external_id, ) self.caused_objects["daily_round"] = str(self.caused_object.id) if self.caused_object.consultation.patient.facility: self.caused_objects["facility"] = str( - self.caused_object.consultation.facility.external_id + self.caused_object.consultation.facility.external_id, ) if isinstance(self.caused_object, ShiftingRequest): self.caused_objects["shifting"] = str(self.caused_object.external_id) @@ -351,18 +373,18 @@ def send_webpush_user(self, user, message): "sub": "mailto:info@coronasafe.network", }, ) - except WebPushException as ex: - print("Web Push Failed with Exception: {}", repr(ex)) - if ex.response and ex.response.json(): - extra = ex.response.json() - print( - "Remote service replied with a {}:{}, {}", + except WebPushException as e: + logger.error("Web Push Failed with Exception: %s", repr(e)) + if e.response and e.response.json(): + extra = e.response.json() + logger.error( + "Web Push Failed, remote service replied with a %s:%s, %s", extra.code, extra.errno, extra.message, ) except Exception as e: - print("Error When Doing WebPush", e) + logger.error("Error When Doing WebPush: %s", repr(e)) def generate(self): if not self.worker_initiated: @@ -372,7 +394,7 @@ def generate(self): medium == Notification.Medium.SMS.value and settings.SEND_SMS_NOTIFICATION ): - sendSMS( + sms.send( self.generate_sms_phone_numbers(), self.generate_sms_message(), many=True, @@ -382,7 +404,9 @@ def generate(self): self.message = self.generate_system_message() for user in self.generate_system_users(): notification_obj = self.generate_message_for_user( - user, self.message, Notification.Medium.SYSTEM.value + user, + self.message, + Notification.Medium.SYSTEM.value, ) if not self.defer_notifications: self.send_webpush_user( @@ -391,6 +415,6 @@ def generate(self): { "external_id": str(notification_obj.external_id), "title": self.message, - } + }, ), ) diff --git a/care/utils/queryset/__init__.py b/care/utils/queryset/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/care/utils/queryset/communications.py b/care/utils/queryset/communications.py index 81ea679f8c..e71a6d3d44 100644 --- a/care/utils/queryset/communications.py +++ b/care/utils/queryset/communications.py @@ -11,12 +11,12 @@ def get_communications(user): queryset = queryset.filter(claim__policy__patient__facility__state=user.state) elif user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: queryset = queryset.filter( - claim__policy__patient__facility__district=user.district + claim__policy__patient__facility__district=user.district, ) else: allowed_facilities = get_accessible_facilities(user) queryset = queryset.filter( - claim__policy__patient__facility__id__in=allowed_facilities + claim__policy__patient__facility__id__in=allowed_facilities, ) return queryset diff --git a/care/utils/serializer/external_id_field.py b/care/utils/serializer/external_id_field.py index b71718c524..a80063880c 100644 --- a/care/utils/serializer/external_id_field.py +++ b/care/utils/serializer/external_id_field.py @@ -1,19 +1,19 @@ import uuid from django.core.exceptions import ObjectDoesNotExist -from rest_framework import serializers from rest_framework.fields import empty +from rest_framework.serializers import Field, ValidationError -class UUIDValidator(object): +class UUIDValidator: def __call__(self, value): try: return uuid.UUID(value) except ValueError: - raise serializers.ValidationError("invalid uuid") + raise ValidationError("invalid uuid") from None -class ExternalIdSerializerField(serializers.Field): +class ExternalIdSerializerField(Field): def __init__(self, queryset=None, *args, **kwargs): super().__init__(*args, **kwargs) self.queryset = queryset @@ -35,5 +35,5 @@ def run_validation(self, data=empty): try: value = self.queryset.get(external_id=value) except ObjectDoesNotExist: - raise serializers.ValidationError("object with this id not found") + raise ValidationError("object with this id not found") from None return value diff --git a/care/utils/sms/sendSMS.py b/care/utils/sms.py similarity index 68% rename from care/utils/sms/sendSMS.py rename to care/utils/sms.py index dbf0fc9edd..e1624ce0f8 100644 --- a/care/utils/sms/sendSMS.py +++ b/care/utils/sms.py @@ -1,10 +1,14 @@ +import logging + import boto3 from django.conf import settings from care.utils.models.validators import mobile_validator +logger = logging.getLogger(__name__) + -def sendSMS(phone_numbers, message, many=False): +def send(phone_numbers, message, many=False): if not many: phone_numbers = [phone_numbers] phone_numbers = list(set(phone_numbers)) @@ -12,12 +16,12 @@ def sendSMS(phone_numbers, message, many=False): try: mobile_validator(phone) except Exception: - continue - client = boto3.client( + logger.error("Invalid Phone Number: %s", phone) + sns = boto3.client( "sns", aws_access_key_id=settings.SNS_ACCESS_KEY, aws_secret_access_key=settings.SNS_SECRET_KEY, region_name=settings.SNS_REGION, ) - client.publish(PhoneNumber=phone, Message=message) + sns.publish(PhoneNumber=phone, Message=message) return True diff --git a/care/utils/tests/test_phone_number_validator.py b/care/utils/tests/test_phone_number_validator.py index 5f378c9954..a246c448c1 100644 --- a/care/utils/tests/test_phone_number_validator.py +++ b/care/utils/tests/test_phone_number_validator.py @@ -8,7 +8,7 @@ class PhoneNumberValidatorTests(TestCase): mobile_validator = PhoneNumberValidator(types=("mobile",)) indian_mobile_validator = PhoneNumberValidator(types=("indian_mobile",)) international_mobile_validator = PhoneNumberValidator( - types=("international_mobile",) + types=("international_mobile",), ) landline_validator = PhoneNumberValidator(types=("landline",)) support_validator = PhoneNumberValidator(types=("support",)) @@ -90,25 +90,29 @@ def test_valid_mobile_numbers(self): def test_valid_indian_mobile_numbers(self): for number in self.valid_indian_mobile_numbers: self.assertIsNone( - self.indian_mobile_validator(number), msg=f"Failed for {number}" + self.indian_mobile_validator(number), + msg=f"Failed for {number}", ) def test_valid_international_mobile_numbers(self): for number in self.valid_international_mobile_numbers: self.assertIsNone( - self.international_mobile_validator(number), msg=f"Failed for {number}" + self.international_mobile_validator(number), + msg=f"Failed for {number}", ) def test_valid_landline_numbers(self): for number in self.valid_landline_numbers: self.assertIsNone( - self.landline_validator(number), msg=f"Failed for {number}" + self.landline_validator(number), + msg=f"Failed for {number}", ) def test_valid_support_numbers(self): for number in self.valid_support_numbers: self.assertIsNone( - self.support_validator(number), msg=f"Failed for {number}" + self.support_validator(number), + msg=f"Failed for {number}", ) def test_invalid_indian_mobile_numbers(self): diff --git a/care/utils/tests/test_utils.py b/care/utils/tests/test_utils.py index d5cb0de873..d257420eeb 100644 --- a/care/utils/tests/test_utils.py +++ b/care/utils/tests/test_utils.py @@ -27,7 +27,7 @@ from care.users.models import District, State -class override_cache(override_settings): +class override_cache(override_settings): # noqa: N801 """ Overrides the cache settings for the test to use a local memory cache instead of the redis cache @@ -40,7 +40,7 @@ def __init__(self, decorated): "default": { "BACKEND": "django.core.cache.backends.locmem.LocMemCache", "LOCATION": f"care-test-{uuid.uuid4()}", - } + }, }, ) @@ -56,18 +56,16 @@ def __eq__(self, other): mock_equal = EverythingEquals() -def assert_equal_dicts(d1, d2, ignore_keys=[]): +def assert_equal_dicts(d1, d2, ignore_keys=None): + if ignore_keys is None: + ignore_keys = [] + def check_equal(): ignored = set(ignore_keys) for k1, v1 in d1.items(): if k1 not in ignored and (k1 not in d2 or d2[k1] != v1): - print(k1, v1, d2[k1]) return False - for k2, v2 in d2.items(): - if k2 not in ignored and k2 not in d1: - print(k2, v2) - return False - return True + return all(not (k2 not in ignored and k2 not in d1) for k2, v2 in d2.items()) return check_equal() @@ -205,7 +203,11 @@ def get_facility_data(cls, district): @classmethod def create_facility( - cls, user: User, district: District, local_body: LocalBody, **kwargs + cls, + user: User, + district: District, + local_body: LocalBody, + **kwargs, ) -> Facility: data = { "name": "Foo", @@ -254,7 +256,7 @@ def get_patient_data(cls, district, state) -> dict: "number_of_chronic_diseased_dependents": 1, "medical_history": [{"disease": "Diabetes", "details": "150 count"}], "date_of_receipt_of_information": make_aware( - datetime(2020, 4, 1, 15, 30, 00) + datetime(2020, 4, 1, 15, 30, 00), ), } @@ -267,9 +269,10 @@ def create_patient(cls, district: District, facility: Facility, **kwargs): { "facility": facility, "disease_status": getattr( - DiseaseStatusEnum, patient_data["disease_status"] + DiseaseStatusEnum, + patient_data["disease_status"], ).value, - } + }, ) patient_data.update(kwargs) @@ -323,7 +326,7 @@ def create_consultation( "patient": patient, "facility": facility, "referred_to": referred_to, - } + }, ) data.update(kwargs) return PatientConsultation.objects.create(**data) @@ -386,10 +389,10 @@ def get_local_body_district_state_representation(self, obj): """ response = {} response.update( - self.get_local_body_representation(getattr(obj, "local_body", None)) + self.get_local_body_representation(getattr(obj, "local_body", None)), ) response.update( - self.get_district_representation(getattr(obj, "district", None)) + self.get_district_representation(getattr(obj, "district", None)), ) response.update(self.get_state_representation(getattr(obj, "state", None))) return response @@ -397,17 +400,16 @@ def get_local_body_district_state_representation(self, obj): def get_local_body_representation(self, local_body: LocalBody): if local_body is None: return {"local_body": None, "local_body_object": None} - else: - return { - "local_body": local_body.id, - "local_body_object": { - "id": local_body.id, - "name": local_body.name, - "district": local_body.district.id, - "localbody_code": local_body.localbody_code, - "body_type": local_body.body_type, - }, - } + return { + "local_body": local_body.id, + "local_body_object": { + "id": local_body.id, + "name": local_body.name, + "district": local_body.district.id, + "localbody_code": local_body.localbody_code, + "body_type": local_body.body_type, + }, + } def get_district_representation(self, district: District): if district is None: @@ -431,22 +433,23 @@ def dict_to_matching_type(d: dict): return {k: to_matching_type(k, v) for k, v in d.items()} def to_matching_type(name: str, value): - if isinstance(value, (OrderedDict, dict)): + if isinstance(value, OrderedDict | dict): return dict_to_matching_type(dict(value)) - elif isinstance(value, list): + if isinstance(value, list): return [to_matching_type("", v) for v in value] - elif "date" in name and not isinstance( - value, (type(None), EverythingEquals) + if "date" in name and not isinstance( + value, + type(None) | EverythingEquals, ): return_value = value if isinstance( value, - ( - str, - unicode, - ), + str | unicode, ): - return_value = datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%fZ") + return_value = datetime.strptime( + value, + "%Y-%m-%dT%H:%M:%S.%f%z", + ) return ( return_value.astimezone(tz=UTC) if isinstance(return_value, datetime) @@ -466,13 +469,12 @@ def execute_list(self, user=None): def get_facility_representation(self, facility): if facility is None: return facility - else: - return { - "id": str(facility.external_id), - "name": facility.name, - "facility_type": { - "id": facility.facility_type, - "name": facility.get_facility_type_display(), - }, - **self.get_local_body_district_state_representation(facility), - } + return { + "id": str(facility.external_id), + "name": facility.name, + "facility_type": { + "id": facility.facility_type, + "name": facility.get_facility_type_display(), + }, + **self.get_local_body_district_state_representation(facility), + } diff --git a/care/utils/validators/__init__.py b/care/utils/validators/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/care/utils/validation/integer_validation.py b/care/utils/validators/integer_validator.py similarity index 96% rename from care/utils/validation/integer_validation.py rename to care/utils/validators/integer_validator.py index cfc75184e7..edde4dcdb3 100644 --- a/care/utils/validation/integer_validation.py +++ b/care/utils/validators/integer_validator.py @@ -8,5 +8,5 @@ def check_integer(vals): try: vals[i] = int(vals[i]) except Exception: - raise ValidationError({"value": "Integer Required"}) + raise ValidationError({"value": "Integer Required"}) from None return vals diff --git a/care/utils/validators/url_validators.py b/care/utils/validators/url_validators.py new file mode 100644 index 0000000000..5663eb585f --- /dev/null +++ b/care/utils/validators/url_validators.py @@ -0,0 +1,12 @@ +from django.core.validators import RegexValidator +from django.utils.translation import gettext_lazy as _ + + +class MiddlewareDomainAddressValidator(RegexValidator): + regex = r"^(?!https?:\/\/)[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*\.[a-zA-Z]{2,}$" + code = "invalid_domain_name" + message = _( + "The domain name is invalid. " + "It should not start with scheme and " + "should not end with a trailing slash.", + ) diff --git a/config/adminlogin.py b/config/adminlogin.py index 56e9bb0957..b36aabbea1 100644 --- a/config/adminlogin.py +++ b/config/adminlogin.py @@ -9,10 +9,10 @@ def login_wrapper(login_func): def admin_login(request, **kwargs): if getattr(request, "limited", False): messages.error( - request, "Too many login attempts, please try again in 20 minutes" + request, + "Too many login attempts, please try again in 20 minutes", ) return redirect(reverse("admin:index")) - else: - return login_func(request, **kwargs) + return login_func(request, **kwargs) return admin_login diff --git a/config/api_router.py b/config/api_router.py index 33b612d8d1..e6d7d4f6dd 100644 --- a/config/api_router.py +++ b/config/api_router.py @@ -93,10 +93,7 @@ from care.users.api.viewsets.users import UserViewSet from care.users.api.viewsets.userskill import UserSkillViewSet -if settings.DEBUG: - router = DefaultRouter() -else: - router = SimpleRouter() +router = DefaultRouter() if settings.DEBUG else SimpleRouter() router.register("users", UserViewSet) user_nested_router = NestedSimpleRouter(router, r"users", lookup="users") @@ -145,7 +142,9 @@ # Summarisation router.register( - "facility_summary", FacilityCapacitySummaryViewSet, basename="summary-facility" + "facility_summary", + FacilityCapacitySummaryViewSet, + basename="summary-facility", ) router.register("patient_summary", PatientSummaryViewSet, basename="summary-patient") router.register("tests_summary", TestsSummaryViewSet, basename="summary-tests") @@ -175,7 +174,6 @@ router.register("investigation/group", InvestigationGroupViewset) router.register("investigation", PatientInvestigationViewSet) -# Ref: https://github.com/alanjds/drf-nested-routers facility_nested_router = NestedSimpleRouter(router, r"facility", lookup="facility") facility_nested_router.register(r"get_users", FacilityUserViewSet) facility_nested_router.register(r"hospital_doctor", HospitalDoctorViewSet) @@ -201,13 +199,16 @@ patient_nested_router.register(r"abha", AbhaViewSet) consultation_nested_router = NestedSimpleRouter( - router, r"consultation", lookup="consultation" + router, + r"consultation", + lookup="consultation", ) consultation_nested_router.register(r"daily_rounds", DailyRoundsViewSet) consultation_nested_router.register(r"investigation", InvestigationValueViewSet) consultation_nested_router.register(r"prescriptions", ConsultationPrescriptionViewSet) consultation_nested_router.register( - r"prescription_administration", MedicineAdministrationViewSet + r"prescription_administration", + MedicineAdministrationViewSet, ) router.register("medibase", MedibaseViewSet, basename="medibase") @@ -224,7 +225,9 @@ if settings.ENABLE_ABDM: router.register("abdm/healthid", ABDMHealthIDViewSet, basename="abdm-healthid") router.register( - "abdm/health_facility", HealthFacilityViewSet, basename="abdm-healthfacility" + "abdm/health_facility", + HealthFacilityViewSet, + basename="abdm-healthfacility", ) app_name = "api" diff --git a/config/authentication.py b/config/authentication.py index 8d5a3e8281..05b5a56af1 100644 --- a/config/authentication.py +++ b/config/authentication.py @@ -16,6 +16,8 @@ from care.facility.models.asset import Asset from care.users.models import User +OPEN_ID_REQUEST_TIMEOUT = 25 + class CustomJWTAuthentication(JWTAuthentication): def authenticate_header(self, request): @@ -29,7 +31,7 @@ def get_validated_token(self, raw_token): { "detail": "Invalid Token, please relogin to continue", "messages": e.detail.get("messages", []), - } + }, ) from e @@ -49,7 +51,7 @@ class MiddlewareAuthentication(JWTAuthentication): auth_header_type_bytes = auth_header_type.encode(HTTP_HEADER_ENCODING) def open_id_authenticate(self, url, token): - public_key = requests.get(url) + public_key = requests.get(url, timeout=OPEN_ID_REQUEST_TIMEOUT) jwk = public_key.json()["keys"][0] public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(jwk)) return jwt.decode(token, key=public_key, algorithms=["RS256"]) @@ -99,7 +101,7 @@ def get_raw_token(self, header): # Assume the header does not contain a JSON web token return None - if len(parts) != 2: + if len(parts) != 2: # noqa: PLR2004 raise AuthenticationFailed( _("Authorization header must contain two space-delimited values"), code="bad_authorization_header", @@ -115,9 +117,9 @@ def get_validated_token(self, url, raw_token): try: return self.open_id_authenticate(url, raw_token) except Exception as e: - print(e) - - raise InvalidToken({"detail": "Given token not valid for any token type"}) + raise InvalidToken( + {"detail": "Given token not valid for any token type"}, + ) from e def get_user(self, validated_token, facility): """ @@ -128,11 +130,11 @@ def get_user(self, validated_token, facility): try: asset_obj = Asset.objects.select_related("current_location__facility").get( - external_id=validated_token["asset_id"] + external_id=validated_token["asset_id"], ) except (Asset.DoesNotExist, ValidationError) as e: raise InvalidToken( - {"detail": "Invalid Asset ID", "messages": [str(e)]} + {"detail": "Invalid Asset ID", "messages": [str(e)]}, ) from e if asset_obj.current_location.facility != facility: @@ -159,11 +161,14 @@ def get_user(self, validated_token, facility): class ABDMAuthentication(JWTAuthentication): def open_id_authenticate(self, url, token): - public_key = requests.get(url) + public_key = requests.get(url, timeout=OPEN_ID_REQUEST_TIMEOUT) jwk = public_key.json()["keys"][0] public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(jwk)) return jwt.decode( - token, key=public_key, audience="account", algorithms=["RS256"] + token, + key=public_key, + audience="account", + algorithms=["RS256"], ) def authenticate_header(self, request): @@ -187,8 +192,7 @@ def get_validated_token(self, url, token): try: return self.open_id_authenticate(url, token) except Exception as e: - print(e) - raise InvalidToken({"detail": f"Invalid Authorization token: {e}"}) + raise InvalidToken({"detail": f"Invalid Authorization token: {e}"}) from e def get_user(self, validated_token): user = User.objects.filter(username=settings.ABDM_USERNAME).first() @@ -214,7 +218,9 @@ class CustomJWTAuthenticationScheme(OpenApiAuthenticationExtension): def get_security_definition(self, auto_schema): return build_bearer_security_scheme_object( - header_name="Authorization", token_prefix="Bearer", bearer_format="JWT" + header_name="Authorization", + token_prefix="Bearer", # noqa: S106 + bearer_format="JWT", ) @@ -232,7 +238,7 @@ def get_security_definition(self, auto_schema): "The scheme requires a valid JWT token in the Authorization header " "along with the facility id in the X-Facility-Id header. " "--The value field is just for preview, filling it will show allowed " - "endpoints.--" + "endpoints.--", ), } diff --git a/config/patient_otp_authentication.py b/config/patient_otp_authentication.py index 7bb558ac0c..935f2c3f3b 100644 --- a/config/patient_otp_authentication.py +++ b/config/patient_otp_authentication.py @@ -48,14 +48,14 @@ def get_validated_token(self, raw_token): "token_class": PatientToken.__name__, "token_type": PatientToken.token_type, "message": e.args[0], - } + }, ) raise InvalidToken( { "detail": _("Given token not valid for any token type"), "messages": messages, - } + }, ) diff --git a/config/patient_otp_token.py b/config/patient_otp_token.py index 3de397ab47..9f83234c4c 100644 --- a/config/patient_otp_token.py +++ b/config/patient_otp_token.py @@ -5,4 +5,4 @@ class PatientToken(Token): lifetime = timedelta(hours=1) - token_type = "patient_login" + token_type = "patient_login" # noqa: S105 diff --git a/config/ratelimit.py b/config/ratelimit.py index 9ba26a2704..03c68154b2 100644 --- a/config/ratelimit.py +++ b/config/ratelimit.py @@ -2,8 +2,10 @@ from django.conf import settings from django_ratelimit.core import is_ratelimited +CAPTCHA_VALIDATION_REQUEST_TIMEOUT = 25 -def GETKEY(group, request): + +def get_key(group, request): return "ratelimit" @@ -16,7 +18,9 @@ def validatecaptcha(request): "response": recaptcha_response, } captcha_response = requests.post( - "https://www.google.com/recaptcha/api/siteverify", data=values + "https://www.google.com/recaptcha/api/siteverify", + data=values, + timeout=CAPTCHA_VALIDATION_REQUEST_TIMEOUT, ) result = captcha_response.json() @@ -27,19 +31,24 @@ def validatecaptcha(request): # refer https://django-ratelimit.readthedocs.io/en/stable/rates.html for rate def ratelimit( - request, group="", keys=[None], rate=settings.DJANGO_RATE_LIMIT, increment=True + request, + group="", + keys=None, + rate=settings.DJANGO_RATE_LIMIT, + increment=True, ): + if keys is None: + keys = [None] if settings.DISABLE_RATELIMIT: return False checkcaptcha = False - for key in keys: - if key == "ip": - group = group + for _key in keys: + if _key == "ip": key = "ip" else: - group = group + "-{}".format(key) - key = GETKEY + group = group + f"-{_key}" + key = get_key if is_ratelimited( request, group=group, @@ -50,9 +59,6 @@ def ratelimit( checkcaptcha = True if checkcaptcha: - if not validatecaptcha(request): - return True - else: - return False + return bool(not validatecaptcha(request)) return False diff --git a/config/serializers.py b/config/serializers.py index 5570ff8ad7..5520e3268a 100644 --- a/config/serializers.py +++ b/config/serializers.py @@ -14,12 +14,4 @@ def to_internal_value(self, data): if isinstance(data, str) and data not in self.choice_strings_to_values: choice_name_map = {v: k for k, v in self._choices.items()} data = choice_name_map.get(data) - return super(ChoiceField, self).to_internal_value(data) - - -class MultipleChoiceField(serializers.MultipleChoiceField): - def to_representation(self, value): - return super(MultipleChoiceField, self).to_representation(value) - - def to_internal_value(self, data): - return super(MultipleChoiceField, self).to_internal_value(data) + return super().to_internal_value(data) diff --git a/config/settings/base.py b/config/settings/base.py index 053c81f882..4f8a2f7b28 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -4,6 +4,7 @@ import base64 import json +import logging from datetime import timedelta from pathlib import Path @@ -15,6 +16,8 @@ from care.utils.csp import config as csp_config from care.utils.jwks.generate_jwk import generate_encoded_jwks +logger = logging.getLogger(__name__) + BASE_DIR = Path(__file__).resolve(strict=True).parent.parent.parent APPS_DIR = BASE_DIR / "care" env = environ.Env() @@ -71,7 +74,7 @@ # http://niwinz.github.io/django-redis/latest/#_memcached_exceptions_behavior "IGNORE_EXCEPTIONS": True, }, - } + }, } @@ -151,7 +154,7 @@ # https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, @@ -197,7 +200,7 @@ STORAGES = { "staticfiles": { "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", - } + }, } # https://whitenoise.readthedocs.io/en/latest/django.html#WHITENOISE_MANIFEST_STRICT @@ -231,7 +234,7 @@ "django.contrib.messages.context_processors.messages", ], }, - } + }, ] # https://docs.djangoproject.com/en/dev/ref/settings/#form-renderer @@ -269,7 +272,8 @@ EMAIL_TIMEOUT = 5 # https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email DEFAULT_FROM_EMAIL = env( - "EMAIL_FROM", default="Open Healthcare Network " + "EMAIL_FROM", + default="Open Healthcare Network ", ) EMAIL_HOST = env("EMAIL_HOST", default="localhost") EMAIL_PORT = env("EMAIL_PORT", default=587) @@ -302,15 +306,15 @@ "formatters": { "verbose": { "format": "%(levelname)s %(asctime)s %(module)s " - "%(process)d %(thread)d %(message)s" - } + "%(process)d %(thread)d %(message)s", + }, }, "handlers": { "console": { "level": "DEBUG", "class": "logging.StreamHandler", "formatter": "verbose", - } + }, }, "root": {"level": "INFO", "handlers": ["console"]}, } @@ -348,10 +352,10 @@ # https://django-rest-framework-simplejwt.readthedocs.io/en/latest/settings.html SIMPLE_JWT = { "ACCESS_TOKEN_LIFETIME": timedelta( - minutes=env("JWT_ACCESS_TOKEN_LIFETIME", default=10) + minutes=env("JWT_ACCESS_TOKEN_LIFETIME", default=10), ), "REFRESH_TOKEN_LIFETIME": timedelta( - minutes=env("JWT_REFRESH_TOKEN_LIFETIME", default=30) + minutes=env("JWT_REFRESH_TOKEN_LIFETIME", default=30), ), "ROTATE_REFRESH_TOKENS": True, "USER_ID_FIELD": "external_id", @@ -406,7 +410,9 @@ # https://github.com/vigneshhari/healthy_django HEALTHY_DJANGO = [ DjangoDatabaseHealthCheck( - "Database", slug="main_database", connection_name="default" + "Database", + slug="main_database", + connection_name="default", ), DjangoCacheHealthCheck("Cache", slug="main_cache", connection_name="default"), ] @@ -425,8 +431,8 @@ "glob:auth*", "plain:migrations", "plain:audit_log", - ] - } + ], + }, }, "models": { "exclude": { @@ -441,7 +447,7 @@ ], "facility.PatientExternalTest": ["name", "address", "mobile_number"], }, - } + }, }, } @@ -479,7 +485,8 @@ default="BKNxrOpAeB_OBfXI-GlRAlw_vUVCc3mD_AkpE74iZj97twMOHXEFUeJqA7bDqGY10O-RmkvG30NaMf5ZWihnT3k", ) VAPID_PRIVATE_KEY = env( - "VAPID_PRIVATE_KEY", default="7mf3OFreFsgFF4jd8A71ZGdVaj8kpJdOto4cFbfAS-s" + "VAPID_PRIVATE_KEY", + default="7mf3OFreFsgFF4jd8A71ZGdVaj8kpJdOto4cFbfAS-s", ) SEND_SMS_NOTIFICATION = False @@ -491,7 +498,7 @@ CLOUD_REGION = env("CLOUD_REGION", default="ap-south-1") if CLOUD_PROVIDER not in csp_config.CSProvider.__members__: - print(f"Warning Invalid CSP Found! {CLOUD_PROVIDER}") + logger.error("invalid CSP found: %s", CLOUD_PROVIDER) if USE_S3 := env.bool("USE_S3", default=False): # aws settings @@ -536,7 +543,7 @@ # open id connect JWKS = JsonWebKey.import_key_set( - json.loads(base64.b64decode(env("JWKS_BASE64", default=generate_encoded_jwks()))) + json.loads(base64.b64decode(env("JWKS_BASE64", default=generate_encoded_jwks()))), ) # ABDM @@ -545,7 +552,8 @@ ABDM_CLIENT_SECRET = env("ABDM_CLIENT_SECRET", default="") ABDM_URL = env("ABDM_URL", default="https://dev.abdm.gov.in") HEALTH_SERVICE_API_URL = env( - "HEALTH_SERVICE_API_URL", default="https://healthidsbx.abdm.gov.in/api" + "HEALTH_SERVICE_API_URL", + default="https://healthidsbx.abdm.gov.in/api", ) ABDM_USERNAME = env("ABDM_USERNAME", default="abdm_user_internal") X_CM_ID = env("X_CM_ID", default="sbx") @@ -556,7 +564,8 @@ # HCX HCX_PROTOCOL_BASE_PATH = env( - "HCX_PROTOCOL_BASE_PATH", default="http://staging-hcx.swasth.app/api/v0.7" + "HCX_PROTOCOL_BASE_PATH", + default="http://staging-hcx.swasth.app/api/v0.7", ) HCX_AUTH_BASE_PATH = env( "HCX_AUTH_BASE_PATH", diff --git a/config/settings/deployment.py b/config/settings/deployment.py index 7595c29784..6f12070e86 100644 --- a/config/settings/deployment.py +++ b/config/settings/deployment.py @@ -31,13 +31,15 @@ SECURE_HSTS_SECONDS = 60 # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-include-subdomains SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool( - "DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True + "DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS", + default=True, ) # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-preload SECURE_HSTS_PRELOAD = env.bool("DJANGO_SECURE_HSTS_PRELOAD", default=True) # https://docs.djangoproject.com/en/dev/ref/middleware/#x-content-type-options-nosniff SECURE_CONTENT_TYPE_NOSNIFF = env.bool( - "DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True + "DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", + default=True, ) # TEMPLATES @@ -50,7 +52,7 @@ "django.template.loaders.filesystem.Loader", "django.template.loaders.app_directories.Loader", ], - ) + ), ] # EMAIL @@ -68,15 +70,15 @@ "formatters": { "verbose": { "format": "%(levelname)s %(asctime)s %(module)s " - "%(process)d %(thread)d %(message)s" - } + "%(process)d %(thread)d %(message)s", + }, }, "handlers": { "console": { "level": "DEBUG", "class": "logging.StreamHandler", "formatter": "verbose", - } + }, }, "root": {"level": "INFO", "handlers": ["console"]}, "loggers": { diff --git a/config/settings/test.py b/config/settings/test.py index e78cde5780..1745452ee3 100644 --- a/config/settings/test.py +++ b/config/settings/test.py @@ -24,7 +24,7 @@ "django.template.loaders.filesystem.Loader", "django.template.loaders.app_directories.Loader", ], - ) + ), ] # EMAIL @@ -40,7 +40,7 @@ CACHES = { "default": { "BACKEND": "django.core.cache.backends.dummy.DummyCache", - } + }, } # for testing retelimit use override_settings decorator SILENCED_SYSTEM_CHECKS = ["django_ratelimit.E003", "django_ratelimit.W001"] @@ -52,20 +52,20 @@ "version": 1, "disable_existing_loggers": False, "formatters": { - "verbose": {"format": "%(levelname)s %(asctime)s %(module)s %(message)s"} + "verbose": {"format": "%(levelname)s %(asctime)s %(module)s %(message)s"}, }, "handlers": { "console": { "level": "DEBUG", "class": "logging.StreamHandler", "formatter": "verbose", - } + }, }, "loggers": { "django.request": { "handlers": ["console"], "level": "ERROR", - } + }, }, "root": { "handlers": ["console"], diff --git a/config/urls.py b/config/urls.py index 46f42e4eca..19ff816a5e 100644 --- a/config/urls.py +++ b/config/urls.py @@ -3,6 +3,7 @@ from django.contrib import admin from django.urls import include, path from django.views import defaults as default_views +from django.views.generic import TemplateView from drf_spectacular.views import ( SpectacularAPIView, SpectacularRedocView, @@ -20,6 +21,11 @@ CoverageElibilityOnCheckView, PreAuthOnSubmitView, ) +from care.users.api.viewsets.auth import ( + AnnotatedTokenVerifyView, + TokenObtainPairView, + TokenRefreshView, +) from care.users.api.viewsets.change_password import ChangePasswordView from care.users.reset_password_views import ( ResetPasswordCheck, @@ -29,17 +35,16 @@ from config import api_router from config.health_views import MiddlewareAuthenticationVerifyView -from .auth_views import AnnotatedTokenVerifyView, TokenObtainPairView, TokenRefreshView -from .views import home_view - urlpatterns = [ - path("", home_view, name="home"), + path("", TemplateView.as_view(template_name="pages/home.html"), name="home"), # Django Admin, use {% url 'admin:index' %} path(settings.ADMIN_URL, admin.site.urls), # Rest API path("api/v1/auth/login/", TokenObtainPairView.as_view(), name="token_obtain_pair"), path( - "api/v1/auth/token/refresh/", TokenRefreshView.as_view(), name="token_refresh" + "api/v1/auth/token/refresh/", + TokenRefreshView.as_view(), + name="token_refresh", ), path( "api/v1/auth/token/verify/", diff --git a/config/utils.py b/config/utils.py deleted file mode 100644 index be3feadf2d..0000000000 --- a/config/utils.py +++ /dev/null @@ -1,2 +0,0 @@ -def get_psql_search_tokens(text, operator="&"): - return f" {operator} ".join([f"{word}:*" for word in text.strip().split(" ")]) diff --git a/config/validators.py b/config/validators.py deleted file mode 100644 index fdf70e1628..0000000000 --- a/config/validators.py +++ /dev/null @@ -1,69 +0,0 @@ -import re - -from django.core.exceptions import ValidationError -from django.core.validators import RegexValidator -from django.utils.translation import gettext_lazy as _ - - -class NumberValidator(object): - def validate(self, password, user=None): - if not re.findall(r"\d", password): - raise ValidationError( - _("The password must contain at least 1 digit, 0-9."), - code="password_no_number", - ) - - def get_help_text(self): - return _("Your password must contain at least 1 digit, 0-9.") - - -class UppercaseValidator(object): - def validate(self, password, user=None): - if not re.findall("[A-Z]", password): - raise ValidationError( - _("The password must contain at least 1 uppercase letter, A-Z."), - code="password_no_upper", - ) - - def get_help_text(self): - return _("Your password must contain at least 1 uppercase letter, A-Z.") - - -class LowercaseValidator(object): - def validate(self, password, user=None): - if not re.findall("[a-z]", password): - raise ValidationError( - _("The password must contain at least 1 lowercase letter, a-z."), - code="password_no_lower", - ) - - def get_help_text(self): - return _("Your password must contain at least 1 lowercase letter, a-z.") - - -class SymbolValidator(object): - def validate(self, password, user=None): - if not re.findall(r"[()[\]{}|\\`~!@#$%^&*_\-+=;:'\",<>./?]", password): - raise ValidationError( - _( - "The password must contain at least 1 symbol: " - + r"()[]{}|\`~!@#$%^&*_-+=;:'\",<>./?" - ), - code="password_no_symbol", - ) - - def get_help_text(self): - return _( - "Your password must contain at least 1 symbol: " - + r"()[]{}|\`~!@#$%^&*_-+=;:'\",<>./?" - ) - - -class MiddlewareDomainAddressValidator(RegexValidator): - regex = r"^(?!https?:\/\/)[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*\.[a-zA-Z]{2,}$" - code = "invalid_domain_name" - message = _( - "The domain name is invalid. " - "It should not start with scheme and " - "should not end with a trailing slash." - ) diff --git a/config/views.py b/config/views.py deleted file mode 100644 index 4d7d7dab44..0000000000 --- a/config/views.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.shortcuts import render - - -def home_view(request): - return render(request, "pages/home.html") diff --git a/config/websocket.py b/config/websocket.py index 81adfbc664..abdf19f9fd 100644 --- a/config/websocket.py +++ b/config/websocket.py @@ -8,6 +8,5 @@ async def websocket_application(scope, receive, send): if event["type"] == "websocket.disconnect": break - if event["type"] == "websocket.receive": - if event["text"] == "ping": - await send({"type": "websocket.send", "text": "pong!"}) + if event["type"] == "websocket.receive" and event["text"] == "ping": + await send({"type": "websocket.send", "text": "pong!"}) diff --git a/manage.py b/manage.py index 73f24a795d..b09f7a66e0 100755 --- a/manage.py +++ b/manage.py @@ -22,7 +22,7 @@ def main(): raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" + "forget to activate a virtual environment?", ) from exc execute_from_command_line(sys.argv) diff --git a/merge_production_dotenvs_in_dotenv.py b/merge_production_dotenvs_in_dotenv.py deleted file mode 100644 index 89c0fbe477..0000000000 --- a/merge_production_dotenvs_in_dotenv.py +++ /dev/null @@ -1,30 +0,0 @@ -import os -from typing import Sequence - -ROOT_DIR_PATH = os.path.dirname(os.path.realpath(__file__)) -PRODUCTION_DOTENVS_DIR_PATH = os.path.join(ROOT_DIR_PATH, ".envs", ".production") -PRODUCTION_DOTENV_FILE_PATHS = [ - os.path.join(PRODUCTION_DOTENVS_DIR_PATH, ".django"), - os.path.join(PRODUCTION_DOTENVS_DIR_PATH, ".postgres"), -] -DOTENV_FILE_PATH = os.path.join(ROOT_DIR_PATH, ".env") - - -def merge( - output_file_path: str, merged_file_paths: Sequence[str], append_linesep: bool = True -) -> None: - with open(output_file_path, "w") as output_file: - for merged_file_path in merged_file_paths: - with open(merged_file_path, "r") as merged_file: - merged_file_content = merged_file.read() - output_file.write(merged_file_content) - if append_linesep: - output_file.write(os.linesep) - - -def main(): - merge(DOTENV_FILE_PATH, PRODUCTION_DOTENV_FILE_PATHS) - - -if __name__ == "__main__": - main()