Skip to content

Commit

Permalink
Merge pull request #2314 from coronasafe/staging
Browse files Browse the repository at this point in the history
Production release v24.30.0
  • Loading branch information
gigincg authored Jul 22, 2024
2 parents c5b4fa3 + b456779 commit 776a8fc
Show file tree
Hide file tree
Showing 14 changed files with 646 additions and 32 deletions.
1 change: 1 addition & 0 deletions care/facility/api/serializers/patient.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ class Meta:
"facility",
"allow_transfer",
"is_active",
"is_expired",
)


Expand Down
20 changes: 20 additions & 0 deletions care/facility/api/serializers/patient_consultation.py
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,7 @@ class PatientConsentSerializer(serializers.ModelSerializer):
id = serializers.CharField(source="external_id", read_only=True)
created_by = UserBaseMinimumSerializer(read_only=True)
archived_by = UserBaseMinimumSerializer(read_only=True)
files = serializers.SerializerMethodField()

class Meta:
model = PatientConsent
Expand All @@ -869,6 +870,7 @@ class Meta:
"id",
"type",
"patient_code_status",
"files",
"archived",
"archived_by",
"archived_date",
Expand All @@ -878,13 +880,31 @@ class Meta:

read_only_fields = (
"id",
"files",
"created_by",
"created_date",
"archived",
"archived_by",
"archived_date",
)

def get_files(self, obj):
from care.facility.api.serializers.file_upload import (
FileUploadListSerializer,
check_permissions,
)

user = self.context["request"].user
file_type = FileUpload.FileType.CONSENT_RECORD
if check_permissions(file_type, obj.external_id, user, "read"):
return FileUploadListSerializer(
FileUpload.objects.filter(
associating_id=obj.external_id, file_type=file_type
),
many=True,
).data
return None

def validate_patient_code_status(self, value):
if value == PatientCodeStatusType.NOT_SPECIFIED:
raise ValidationError(
Expand Down
2 changes: 1 addition & 1 deletion care/facility/api/viewsets/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ class AssetViewSet(
lookup_field = "external_id"
filter_backends = (filters.DjangoFilterBackend, drf_filters.SearchFilter)
search_fields = ["name", "serial_number", "qr_code_id"]
permission_classes = [IsAuthenticated]
permission_classes = (IsAuthenticated, DRYPermissions)
filterset_class = AssetFilter

def get_queryset(self):
Expand Down
2 changes: 1 addition & 1 deletion care/facility/api/viewsets/encounter_symptom.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def filter_is_cured(self, queryset, name, value):
class EncounterSymptomViewSet(ModelViewSet):
serializer_class = EncounterSymptomSerializer
permission_classes = (IsAuthenticated, DRYPermissions)
queryset = EncounterSymptom.objects.all()
queryset = EncounterSymptom.objects.select_related("created_by", "updated_by")
filter_backends = (filters.DjangoFilterBackend,)
filterset_class = EncounterSymptomFilter
lookup_field = "external_id"
Expand Down
34 changes: 34 additions & 0 deletions care/facility/api/viewsets/patient.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,32 @@ def filter_by_diagnoses(self, queryset, name, value):
)
return queryset.filter(filter_q)

last_consultation__consent_types = MultiSelectFilter(
method="filter_by_has_consents"
)

def filter_by_has_consents(self, queryset, name, value: str):

if not value:
return queryset

values = value.split(",")

filter_q = Q()

if "None" in values:
filter_q |= ~Q(
last_consultation__has_consents__len__gt=0,
)
values.remove("None")

if values:
filter_q |= Q(
last_consultation__has_consents__overlap=values,
)

return queryset.filter(filter_q)


class PatientDRYFilter(DRYPermissionFiltersBase):
def filter_queryset(self, request, queryset, view):
Expand Down Expand Up @@ -565,6 +591,14 @@ def transfer(self, request, *args, **kwargs):
patient = PatientRegistration.objects.get(external_id=kwargs["external_id"])
facility = Facility.objects.get(external_id=request.data["facility"])

if patient.is_expired:
return Response(
{
"Patient": "Patient transfer cannot be completed because the patient is expired"
},
status=status.HTTP_406_NOT_ACCEPTABLE,
)

if patient.is_active and facility == patient.facility:
return Response(
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Generated by Django 4.2.10 on 2024-07-04 16:20

import uuid

import django.contrib.postgres.fields
import django.db.models.deletion
from django.db import migrations, models
from django.db.models import Subquery


class Migration(migrations.Migration):

def migrate_has_consents(apps, schema_editor):
FileUpload = apps.get_model("facility", "FileUpload")
PatientConsent = apps.get_model("facility", "PatientConsent")

consents = PatientConsent.objects.filter(archived=False)
for consent in consents:
consultation = consent.consultation
consent_types = (
PatientConsent.objects.filter(consultation=consultation, archived=False)
.annotate(
str_external_id=models.functions.Cast(
"external_id", models.CharField()
)
)
.annotate(
has_files=models.Exists(
FileUpload.objects.filter(
associating_id=models.OuterRef("str_external_id"),
file_type=7,
is_archived=False,
)
)
)
.filter(has_files=True)
.distinct("type")
.values_list("type", flat=True)
)
consultation.has_consents = list(consent_types)
consultation.save()

dependencies = [
("facility", "0443_remove_patientconsultation_consent_records_and_more"),
]

operations = [
migrations.AddField(
model_name="patientconsultation",
name="has_consents",
field=django.contrib.postgres.fields.ArrayField(
base_field=models.IntegerField(
choices=[
(1, "Consent for Admission"),
(2, "Patient Code Status"),
(3, "Consent for Procedure"),
(4, "High Risk Consent"),
(5, "Others"),
]
),
default=list,
size=None,
),
),
migrations.AlterField(
model_name="patientconsent",
name="consultation",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="consents",
to="facility.patientconsultation",
),
),
migrations.RunPython(
migrate_has_consents, reverse_code=migrations.RunPython.noop
),
]
13 changes: 13 additions & 0 deletions care/facility/migrations/0445_merge_20240715_0301.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Generated by Django 4.2.10 on 2024-07-14 21:31

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("facility", "0444_alter_medicineadministration_dosage_and_more"),
("facility", "0444_patientconsultation_has_consents_and_more"),
]

operations = []
20 changes: 20 additions & 0 deletions care/facility/models/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,26 @@ def delete(self, *args, **kwargs):
AssetBed.objects.filter(asset=self).update(deleted=True)
super().delete(*args, **kwargs)

@staticmethod
def has_write_permission(request):
if request.user.asset or request.user.user_type in User.READ_ONLY_TYPES:
return False
return (
request.user.is_superuser
or request.user.verified
and request.user.user_type >= User.TYPE_VALUE_MAP["Staff"]
)

def has_object_write_permission(self, request):
return self.has_write_permission(request)

@staticmethod
def has_read_permission(request):
return request.user.is_superuser or request.user.verified

def has_object_read_permission(self, request):
return self.has_read_permission(request)

def __str__(self):
return self.name

Expand Down
41 changes: 41 additions & 0 deletions care/facility/models/file_upload.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import time
import uuid
from uuid import uuid4

import boto3
Expand Down Expand Up @@ -163,5 +164,45 @@ class FileType(models.IntegerChoices):
FileTypeChoices = [(x.value, x.name) for x in FileType]
FileCategoryChoices = [(x.value, x.name) for x in BaseFileUpload.FileCategory]

def save(self, *args, **kwargs):
from care.facility.models import PatientConsent

if self.file_type == self.FileType.CONSENT_RECORD:
new_consent = False
if not self.pk and not self.is_archived:
new_consent = True
consent = PatientConsent.objects.filter(
external_id=uuid.UUID(self.associating_id), archived=False
).first()
consultation = consent.consultation
consent_types = (
PatientConsent.objects.filter(consultation=consultation, archived=False)
.annotate(
str_external_id=models.functions.Cast(
"external_id", models.CharField()
)
)
.annotate(
has_files=(
models.Exists(
FileUpload.objects.filter(
associating_id=models.OuterRef("str_external_id"),
file_type=self.FileType.CONSENT_RECORD,
is_archived=False,
).exclude(pk=self.pk if self.is_archived else None)
)
if not new_consent
else models.Value(True)
)
)
.filter(has_files=True)
.distinct("type")
.values_list("type", flat=True)
)
consultation.has_consents = list(consent_types)
consultation.save()

return super().save(*args, **kwargs)

def __str__(self):
return f"{self.FileTypeChoices[self.file_type][1]} - {self.name}{' (Archived)' if self.is_archived else ''}"
4 changes: 4 additions & 0 deletions care/facility/models/patient.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,10 @@ class TestTypeEnum(enum.Enum):

objects = BaseManager()

@property
def is_expired(self) -> bool:
return self.death_datetime is not None

def __str__(self):
return f"{self.name} - {self.year_of_birth} - {self.get_gender_display()}"

Expand Down
25 changes: 16 additions & 9 deletions care/facility/models/patient_consultation.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@
from care.utils.models.base import BaseModel


class ConsentType(models.IntegerChoices):
CONSENT_FOR_ADMISSION = 1, "Consent for Admission"
PATIENT_CODE_STATUS = 2, "Patient Code Status"
CONSENT_FOR_PROCEDURE = 3, "Consent for Procedure"
HIGH_RISK_CONSENT = 4, "High Risk Consent"
OTHERS = 5, "Others"


class PatientConsultation(PatientBaseModel, ConsultationRelatedPermissionMixin):
SUGGESTION_CHOICES = [
(SuggestionChoices.HI, "HOME ISOLATION"),
Expand Down Expand Up @@ -248,6 +256,11 @@ class PatientConsultation(PatientBaseModel, ConsultationRelatedPermissionMixin):
prn_prescription = JSONField(default=dict)
discharge_advice = JSONField(default=dict)

has_consents = ArrayField(
models.IntegerField(choices=ConsentType.choices),
default=list,
)

def get_related_consultation(self):
return self

Expand Down Expand Up @@ -359,14 +372,6 @@ def has_object_generate_discharge_summary_permission(self, request):
return self.has_object_read_permission(request)


class ConsentType(models.IntegerChoices):
CONSENT_FOR_ADMISSION = 1, "Consent for Admission"
PATIENT_CODE_STATUS = 2, "Patient Code Status"
CONSENT_FOR_PROCEDURE = 3, "Consent for Procedure"
HIGH_RISK_CONSENT = 4, "High Risk Consent"
OTHERS = 5, "Others"


class PatientCodeStatusType(models.IntegerChoices):
NOT_SPECIFIED = 0, "Not Specified"
DNH = 1, "Do Not Hospitalize"
Expand All @@ -387,7 +392,9 @@ class ConsultationClinician(models.Model):


class PatientConsent(BaseModel, ConsultationRelatedPermissionMixin):
consultation = models.ForeignKey(PatientConsultation, on_delete=models.CASCADE)
consultation = models.ForeignKey(
PatientConsultation, on_delete=models.CASCADE, related_name="consents"
)
type = models.IntegerField(choices=ConsentType.choices)
patient_code_status = models.IntegerField(
choices=PatientCodeStatusType.choices, null=True, blank=True
Expand Down
16 changes: 16 additions & 0 deletions care/facility/tests/test_asset_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from rest_framework.test import APITestCase

from care.facility.models import Asset, Bed
from care.users.models import User
from care.utils.assetintegration.asset_classes import AssetClasses
from care.utils.tests.test_utils import TestUtils

Expand All @@ -17,6 +18,11 @@ def setUpTestData(cls) -> None:
cls.facility = cls.create_facility(cls.super_user, cls.district, cls.local_body)
cls.asset_location = cls.create_asset_location(cls.facility)
cls.user = cls.create_user("staff", cls.district, home_facility=cls.facility)
cls.state_admin_ro = cls.create_user(
"stateadmin-ro",
cls.district,
user_type=User.TYPE_VALUE_MAP["StateReadOnlyAdmin"],
)
cls.patient = cls.create_patient(
cls.district, cls.facility, local_body=cls.local_body
)
Expand All @@ -38,6 +44,16 @@ def test_create_asset(self):
response = self.client.post("/api/v1/asset/", sample_data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)

def test_create_asset_read_only(self):
sample_data = {
"name": "Test Asset",
"asset_type": 50,
"location": self.asset_location.external_id,
}
self.client.force_authenticate(self.state_admin_ro)
response = self.client.post("/api/v1/asset/", sample_data)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

def test_create_asset_with_warranty_past(self):
sample_data = {
"name": "Test Asset",
Expand Down
Loading

0 comments on commit 776a8fc

Please sign in to comment.