Skip to content

Commit

Permalink
Merge branch 'master' into sainak/fix/username-validation
Browse files Browse the repository at this point in the history
  • Loading branch information
sainak authored Feb 10, 2024
2 parents 9515dfb + c7842be commit 4e8a369
Show file tree
Hide file tree
Showing 32 changed files with 409 additions and 56 deletions.
17 changes: 15 additions & 2 deletions care/abdm/api/viewsets/auth.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import logging
from datetime import datetime, timedelta

from django.core.cache import cache
Expand All @@ -14,6 +15,8 @@
from care.facility.models.patient_consultation import PatientConsultation
from config.authentication import ABDMAuthentication

logger = logging.getLogger(__name__)


class OnFetchView(GenericAPIView):
permission_classes = (IsAuthenticated,)
Expand All @@ -25,7 +28,14 @@ def post(self, request, *args, **kwargs):
try:
AbdmGateway().init(data["resp"]["requestId"])
except Exception as e:
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
logger.warning(
f"Error: OnFetchView::post failed while initialising ABDM Gateway, Reason: {e}",
exc_info=True,
)
return Response(
{"detail": "Error: Initialising ABDM Gateway failed."},
status=status.HTTP_400_BAD_REQUEST,
)

return Response({}, status=status.HTTP_202_ACCEPTED)

Expand Down Expand Up @@ -346,10 +356,13 @@ def post(self, request, *args, **kwargs):
}
)
except Exception as e:
logger.warning(
f"Error: RequestDataView::post failed to notify (health-information/notify). Reason: {e}",
exc_info=True,
)
return Response(
{
"detail": "Failed to notify (health-information/notify)",
"error": str(e),
},
status=status.HTTP_400_BAD_REQUEST,
)
Expand Down
25 changes: 21 additions & 4 deletions care/abdm/api/viewsets/health_facility.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import re

from celery import shared_task
from django.conf import settings
from dry_rest_permissions.generics import DRYPermissions
Expand Down Expand Up @@ -30,14 +32,17 @@ def register_health_facility_as_service(facility_external_id):
if health_facility.registered:
return [True, None]

clean_facility_name = re.sub(r"[^A-Za-z0-9 ]+", " ", health_facility.facility.name)
clean_facility_name = re.sub(r"\s+", " ", clean_facility_name).strip()
hip_name = settings.HIP_NAME_PREFIX + clean_facility_name + settings.HIP_NAME_SUFFIX
response = Facility().add_update_service(
{
"facilityId": health_facility.hf_id,
"facilityName": health_facility.facility.name,
"facilityName": hip_name,
"HRP": [
{
"bridgeId": settings.ABDM_CLIENT_ID,
"hipName": health_facility.facility.name,
"hipName": hip_name,
"type": "HIP",
"active": True,
"alias": ["CARE_HIP"],
Expand All @@ -50,7 +55,19 @@ def register_health_facility_as_service(facility_external_id):
data = response.json()[0]

if "error" in data:
return [False, data["error"]["message"]]
if (
data["error"].get("code") == "2500"
and settings.ABDM_CLIENT_ID in data["error"].get("message")
and "already associated" in data["error"].get("message")
):
health_facility.registered = True
health_facility.save()
return [True, None]

return [
False,
data["error"].get("message", "Error while registering HIP as service"),
]

if "servicesLinked" in data:
health_facility.registered = True
Expand Down Expand Up @@ -83,7 +100,7 @@ def register_service(self, request, facility__external_id):
[registered, error] = register_health_facility_as_service(facility__external_id)

if error:
return Response({"error": error}, status=400)
return Response({"detail": error}, status=400)

return Response({"registered": registered})

Expand Down
36 changes: 32 additions & 4 deletions care/abdm/api/viewsets/healthid.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# ABDM HealthID APIs

import logging
from datetime import datetime

from drf_spectacular.utils import extend_schema
Expand Down Expand Up @@ -31,6 +32,8 @@
from config.auth_views import CaptchaRequiredException
from config.ratelimit import ratelimit

logger = logging.getLogger(__name__)


# API for Generating OTP for HealthID
class ABDMHealthIDViewSet(GenericViewSet, CreateModelMixin):
Expand Down Expand Up @@ -363,8 +366,14 @@ def link_via_qr(self, request):
}
)
except Exception as e:
logger.warning(
f"Error: ABDMHealthIDViewSet::link_via_qr failed to fetch modes. Reason: {e}",
exc_info=True,
)
return Response(
{"detail": "Failed to fetch modes", "error": str(e)},
{
"detail": "Failed to fetch modes",
},
status=status.HTTP_400_BAD_REQUEST,
)

Expand Down Expand Up @@ -419,8 +428,15 @@ def get_new_linking_token(self, request):
}
)
except Exception as e:
logger.warning(
f"Error: ABDMHealthIDViewSet::get_new_linking_token failed to fetch modes. Reason: {e}",
exc_info=True,
)

return Response(
{"detail": "Failed to fetch modes", "error": str(e)},
{
"detail": "Failed to fetch modes",
},
status=status.HTTP_400_BAD_REQUEST,
)

Expand Down Expand Up @@ -463,8 +479,15 @@ def add_care_context(self, request, *args, **kwargs):
}
)
except Exception as e:
logger.warning(
f"Error: ABDMHealthIDViewSet::add_care_context failed. Reason: {e}",
exc_info=True,
)

return Response(
{"detail": "Failed to add care context", "error": str(e)},
{
"detail": "Failed to add care context",
},
status=status.HTTP_400_BAD_REQUEST,
)

Expand Down Expand Up @@ -496,8 +519,13 @@ def patient_sms_notify(self, request, *args, **kwargs):
}
)
except Exception as e:
logger.warning(
f"Error: ABDMHealthIDViewSet::patient_sms_notify failed to send SMS. Reason: {e}",
exc_info=True,
)

return Response(
{"detail": "Failed to send SMS", "error": str(e)},
{"detail": "Failed to send SMS"},
status=status.HTTP_400_BAD_REQUEST,
)

Expand Down
2 changes: 0 additions & 2 deletions care/facility/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
Inventory,
InventoryItem,
InventoryLog,
MetaICD11Diagnosis,
PatientExternalTest,
PatientInvestigation,
PatientInvestigationGroup,
Expand Down Expand Up @@ -212,4 +211,3 @@ class FacilityUserAdmin(DjangoQLSearchMixin, admin.ModelAdmin, ExportCsvMixin):
admin.site.register(AssetBed)
admin.site.register(Asset)
admin.site.register(Bed)
admin.site.register(MetaICD11Diagnosis)
6 changes: 5 additions & 1 deletion care/facility/api/serializers/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,15 @@ class AssetSerializer(ModelSerializer):
last_serviced_on = serializers.DateField(write_only=True, required=False)
note = serializers.CharField(write_only=True, required=False, allow_blank=True)
resolved_middleware = ResolvedMiddlewareField(read_only=True)
latest_status = serializers.CharField(read_only=True)

class Meta:
model = Asset
exclude = ("deleted", "external_id", "current_location")
read_only_fields = TIMESTAMP_FIELDS + ("resolved_middleware",)
read_only_fields = TIMESTAMP_FIELDS + (
"resolved_middleware",
"latest_status",
)

def validate_qr_code_id(self, value):
value = value or None # treat empty string as null
Expand Down
5 changes: 5 additions & 0 deletions care/facility/api/serializers/bed.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from rest_framework.exceptions import ValidationError
from rest_framework.serializers import (
BooleanField,
CharField,
DateTimeField,
IntegerField,
ListField,
Expand Down Expand Up @@ -35,6 +36,7 @@

class BedSerializer(ModelSerializer):
id = UUIDField(source="external_id", read_only=True)
name = CharField(max_length=1024, required=True)
bed_type = ChoiceField(choices=BedTypeChoices)

location_object = AssetLocationSerializer(source="location", read_only=True)
Expand All @@ -45,6 +47,9 @@ class BedSerializer(ModelSerializer):

number_of_beds = IntegerField(required=False, default=1, write_only=True)

def validate_name(self, value):
return value.strip() if value else value

def validate_number_of_beds(self, value):
if value > 100:
raise ValidationError("Cannot create more than 100 beds at once.")
Expand Down
13 changes: 12 additions & 1 deletion care/facility/api/serializers/patient_consultation.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from datetime import timedelta

from django.conf import settings
from django.db import transaction
from django.utils.timezone import localtime, now
from django.utils.timezone import localtime, make_aware, now
from rest_framework import serializers
from rest_framework.exceptions import ValidationError

Expand Down Expand Up @@ -47,6 +48,8 @@
from care.utils.serializer.external_id_field import ExternalIdSerializerField
from config.serializers import ChoiceField

MIN_ENCOUNTER_DATE = make_aware(settings.MIN_ENCOUNTER_DATE)


class PatientConsultationSerializer(serializers.ModelSerializer):
id = serializers.CharField(source="external_id", read_only=True)
Expand Down Expand Up @@ -474,6 +477,14 @@ def validate_create_diagnoses(self, value):
return value

def validate_encounter_date(self, value):
if value < MIN_ENCOUNTER_DATE:
raise ValidationError(
{
"encounter_date": [
f"This field value must be greater than {MIN_ENCOUNTER_DATE.strftime('%Y-%m-%d')}"
]
}
)
if value > now():
raise ValidationError(
{"encounter_date": "This field value cannot be in the future."}
Expand Down
19 changes: 18 additions & 1 deletion care/facility/api/viewsets/asset.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.conf import settings
from django.core.cache import cache
from django.db.models import Exists, OuterRef, Q
from django.db.models import Exists, OuterRef, Q, Subquery
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.http import Http404
Expand Down Expand Up @@ -73,6 +73,7 @@ class AssetLocationViewSet(
CreateModelMixin,
UpdateModelMixin,
GenericViewSet,
DestroyModelMixin,
):
queryset = (
AssetLocation.objects.all().select_related("facility").order_by("-created_date")
Expand Down Expand Up @@ -118,6 +119,15 @@ def get_facility(self):
def perform_create(self, serializer):
serializer.save(facility=self.get_facility())

def destroy(self, request, *args, **kwargs):
instance = self.get_object()
if instance.bed_set.filter(deleted=False).count():
raise ValidationError("Cannot delete a Location with associated Beds")
if instance.asset_set.filter(deleted=False).count():
raise ValidationError("Cannot delete a Location with associated Assets")

return super().destroy(request, *args, **kwargs)


class AssetFilter(filters.FilterSet):
facility = filters.UUIDFilter(field_name="current_location__facility__external_id")
Expand Down Expand Up @@ -252,6 +262,13 @@ def get_queryset(self):
queryset = queryset.filter(
current_location__facility__id__in=allowed_facilities
)
queryset = queryset.annotate(
latest_status=Subquery(
AssetAvailabilityRecord.objects.filter(asset=OuterRef("pk"))
.order_by("-created_date")
.values("status")[:1]
)
)
return queryset

def list(self, request, *args, **kwargs):
Expand Down
16 changes: 12 additions & 4 deletions care/facility/api/viewsets/bed.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.core.exceptions import ValidationError as DjangoValidationError
from django.db import IntegrityError, transaction
from django.db.models import OuterRef, Subquery
from django_filters import rest_framework as filters
from drf_spectacular.utils import extend_schema, extend_schema_view
Expand Down Expand Up @@ -73,22 +74,29 @@ def get_queryset(self):
queryset = queryset.filter(facility__id__in=allowed_facilities)
return queryset

@transaction.atomic
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
number_of_beds = serializer.validated_data.pop("number_of_beds", 1)
# Bulk creating n number of beds
if number_of_beds > 1:
data = serializer.validated_data.copy()
data.pop("name")
name = data.pop("name")
beds = [
Bed(
**data,
name=f"{serializer.validated_data['name']} {i+1}",
name=f"{name} {i}",
)
for i in range(number_of_beds)
for i in range(1, number_of_beds + 1)
]
Bed.objects.bulk_create(beds)
try:
Bed.objects.bulk_create(beds)
except IntegrityError:
return Response(
{"detail": "Bed with same name already exists in this location."},
status=status.HTTP_400_BAD_REQUEST,
)
return Response(status=status.HTTP_201_CREATED)

self.perform_create(serializer)
Expand Down
4 changes: 4 additions & 0 deletions care/facility/api/viewsets/patient.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ def filter_by_category(self, queryset, name, value):
field_name="last_vaccinated_date"
)
is_antenatal = filters.BooleanFilter(field_name="is_antenatal")
last_menstruation_start_date = filters.DateFromToRangeFilter(
field_name="last_menstruation_start_date"
)
date_of_delivery = filters.DateFromToRangeFilter(field_name="date_of_delivery")
is_active = filters.BooleanFilter(field_name="is_active")
# Location Based Filtering
district = filters.NumberFilter(field_name="district__id")
Expand Down
2 changes: 1 addition & 1 deletion care/facility/api/viewsets/patient_investigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class PatientInvestigationViewSet(
mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
):
serializer_class = PatientInvestigationSerializer
queryset = PatientInvestigation.objects.all()
queryset = PatientInvestigation.objects.all().select_related("groups")
lookup_field = "external_id"
permission_classes = (IsAuthenticated,)
filterset_class = PatientInvestigationFilter
Expand Down
Loading

0 comments on commit 4e8a369

Please sign in to comment.