diff --git a/care/facility/api/serializers/bed.py b/care/facility/api/serializers/bed.py index 41597e186d..508c2f9619 100644 --- a/care/facility/api/serializers/bed.py +++ b/care/facility/api/serializers/bed.py @@ -123,11 +123,14 @@ def validate(self, attrs): {"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 - ).exists() - ): + asset.asset_class + in [ + AssetClasses.HL7MONITOR.name, + AssetClasses.ONVIF.name, + ] + ) and AssetBed.objects.filter( + bed=bed, asset__asset_class=asset.asset_class + ).exists(): raise ValidationError( { "asset": "Bed is already in use by another asset of the same class" diff --git a/care/facility/api/serializers/camera_preset.py b/care/facility/api/serializers/camera_preset.py new file mode 100644 index 0000000000..7157b5245a --- /dev/null +++ b/care/facility/api/serializers/camera_preset.py @@ -0,0 +1,49 @@ +from rest_framework import serializers +from rest_framework.exceptions import ValidationError + +from care.facility.api.serializers.bed import AssetBedSerializer +from care.facility.models import CameraPreset +from care.users.api.serializers.user import UserBaseMinimumSerializer + + +class CameraPresetSerializer(serializers.ModelSerializer): + id = serializers.UUIDField(source="external_id", read_only=True) + created_by = UserBaseMinimumSerializer(read_only=True) + updated_by = UserBaseMinimumSerializer(read_only=True) + asset_bed = AssetBedSerializer(read_only=True) + + class Meta: + model = CameraPreset + exclude = ( + "external_id", + "deleted", + ) + read_only_fields = ( + "created_date", + "modified_date", + "is_migrated", + "created_by", + "updated_by", + ) + + def get_asset_bed_obj(self): + return ( + self.instance.asset_bed if self.instance else self.context.get("asset_bed") + ) + + def validate_name(self, value): + if CameraPreset.objects.filter( + asset_bed__bed_id=self.get_asset_bed_obj().bed_id, name=value + ).exists(): + msg = "Name should be unique. Another preset related to this bed already uses the same name." + raise ValidationError(msg) + return value + + def create(self, validated_data): + validated_data["created_by"] = self.context["request"].user + validated_data["asset_bed"] = self.get_asset_bed_obj() + return super().create(validated_data) + + def update(self, instance, validated_data): + validated_data["updated_by"] = self.context["request"].user + return super().update(instance, validated_data) diff --git a/care/facility/api/viewsets/asset.py b/care/facility/api/viewsets/asset.py index 15dd00e2aa..fc66eff4bf 100644 --- a/care/facility/api/viewsets/asset.py +++ b/care/facility/api/viewsets/asset.py @@ -62,6 +62,7 @@ from care.utils.assetintegration.asset_classes import AssetClasses from care.utils.cache.cache_allowed_facilities import get_accessible_facilities from care.utils.filters.choicefilter import CareChoiceFilter, inverse_choices +from care.utils.queryset.asset_bed import get_asset_queryset from care.utils.queryset.asset_location import get_asset_location_queryset from care.utils.queryset.facility import get_facility_queryset from config.authentication import MiddlewareAuthentication @@ -290,21 +291,7 @@ class AssetViewSet( filterset_class = AssetFilter def get_queryset(self): - user = self.request.user - queryset = self.queryset - if user.is_superuser: - pass - elif user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: - 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 - ) - else: - allowed_facilities = get_accessible_facilities(user) - queryset = queryset.filter( - current_location__facility__id__in=allowed_facilities - ) + queryset = get_asset_queryset(user=self.request.user, queryset=self.queryset) return queryset.annotate( latest_status=Subquery( AvailabilityRecord.objects.filter( diff --git a/care/facility/api/viewsets/bed.py b/care/facility/api/viewsets/bed.py index 336b5f83c2..db9dd6652f 100644 --- a/care/facility/api/viewsets/bed.py +++ b/care/facility/api/viewsets/bed.py @@ -30,6 +30,7 @@ from care.users.models import User from care.utils.cache.cache_allowed_facilities import get_accessible_facilities from care.utils.filters.choicefilter import CareChoiceFilter, inverse_choices +from care.utils.queryset.asset_bed import get_asset_bed_queryset, get_bed_queryset inverse_bed_type = inverse_choices(BedTypeChoices) @@ -76,27 +77,14 @@ class BedViewSet( filterset_class = BedFilter def get_queryset(self): - user = self.request.user - queryset = self.queryset - - queryset = queryset.annotate( + queryset = self.queryset.annotate( is_occupied=Exists( ConsultationBed.objects.filter( bed__id=OuterRef("id"), end_date__isnull=True ) ) ) - - if user.is_superuser: - pass - elif user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: - queryset = queryset.filter(facility__state=user.state) - elif user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: - queryset = queryset.filter(facility__district=user.district) - else: - allowed_facilities = get_accessible_facilities(user) - queryset = queryset.filter(facility__id__in=allowed_facilities) - return queryset + return get_bed_queryset(user=self.request.user, queryset=queryset) @transaction.atomic def create(self, request, *args, **kwargs): @@ -168,18 +156,7 @@ class AssetBedViewSet( lookup_field = "external_id" def get_queryset(self): - user = self.request.user - queryset = self.queryset - if user.is_superuser: - pass - elif user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: - queryset = queryset.filter(bed__facility__state=user.state) - elif user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: - queryset = queryset.filter(bed__facility__district=user.district) - else: - allowed_facilities = get_accessible_facilities(user) - queryset = queryset.filter(bed__facility__id__in=allowed_facilities) - return queryset + return get_asset_bed_queryset(user=self.request.user, queryset=self.queryset) class PatientAssetBedFilter(filters.FilterSet): @@ -212,20 +189,9 @@ class PatientAssetBedViewSet(ListModelMixin, GenericViewSet): ] def get_queryset(self): - user = self.request.user - queryset = self.queryset - if user.is_superuser: - pass - elif user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: - queryset = queryset.filter(bed__facility__state=user.state) - elif user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: - queryset = queryset.filter(bed__facility__district=user.district) - else: - 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"] - ) + return get_asset_bed_queryset( + user=self.request.user, queryset=self.queryset + ).filter(bed__facility__external_id=self.kwargs["facility_external_id"]) class ConsultationBedFilter(filters.FilterSet): diff --git a/care/facility/api/viewsets/camera_preset.py b/care/facility/api/viewsets/camera_preset.py new file mode 100644 index 0000000000..bfb168834b --- /dev/null +++ b/care/facility/api/viewsets/camera_preset.py @@ -0,0 +1,63 @@ +from django.shortcuts import get_object_or_404 +from rest_framework.exceptions import NotFound +from rest_framework.mixins import ListModelMixin +from rest_framework.permissions import IsAuthenticated +from rest_framework.viewsets import GenericViewSet, ModelViewSet + +from care.facility.api.serializers.camera_preset import CameraPresetSerializer +from care.facility.models import CameraPreset +from care.utils.queryset.asset_bed import ( + get_asset_bed_queryset, + get_asset_queryset, + get_bed_queryset, +) + + +class AssetBedCameraPresetViewSet(ModelViewSet): + serializer_class = CameraPresetSerializer + queryset = CameraPreset.objects.all().select_related( + "asset_bed", "created_by", "updated_by" + ) + lookup_field = "external_id" + permission_classes = (IsAuthenticated,) + + def get_asset_bed_obj(self): + queryset = get_asset_bed_queryset(self.request.user).filter( + external_id=self.kwargs["assetbed_external_id"] + ) + return get_object_or_404(queryset) + + def get_queryset(self): + return super().get_queryset().filter(asset_bed=self.get_asset_bed_obj()) + + def get_serializer_context(self): + context = super().get_serializer_context() + context["asset_bed"] = self.get_asset_bed_obj() + return context + + +class CameraPresetViewSet(GenericViewSet, ListModelMixin): + serializer_class = CameraPresetSerializer + queryset = CameraPreset.objects.all().select_related( + "asset_bed", "created_by", "updated_by" + ) + lookup_field = "external_id" + permission_classes = (IsAuthenticated,) + + def get_bed_obj(self, external_id: str): + queryset = get_bed_queryset(self.request.user).filter(external_id=external_id) + return get_object_or_404(queryset) + + def get_asset_obj(self, external_id: str): + queryset = get_asset_queryset(self.request.user).filter(external_id=external_id) + return get_object_or_404(queryset) + + def get_queryset(self): + queryset = super().get_queryset() + if asset_external_id := self.kwargs.get("asset_external_id"): + return queryset.filter( + asset_bed__asset=self.get_asset_obj(asset_external_id) + ) + if bed_external_id := self.kwargs.get("bed_external_id"): + return queryset.filter(asset_bed__bed=self.get_bed_obj(bed_external_id)) + raise NotFound diff --git a/care/facility/migrations/0466_camera_presets.py b/care/facility/migrations/0466_camera_presets.py new file mode 100644 index 0000000000..8ee6942342 --- /dev/null +++ b/care/facility/migrations/0466_camera_presets.py @@ -0,0 +1,169 @@ +# Generated by Django 4.2.8 on 2024-05-30 06:56 + +import uuid + +import django.db.models.deletion +from django.conf import settings +from django.core.paginator import Paginator +from django.db import migrations, models +from django.db.models import F, Window +from django.db.models.functions import RowNumber + +import care.utils.models.validators + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("facility", "0465_merge_20240923_1045"), + ] + + def delete_asset_beds_without_asset_class(apps, schema_editor): + AssetBed = apps.get_model("facility", "AssetBed") + AssetBed.objects.filter(asset__asset_class__isnull=True).delete() + + def backfill_camera_presets(apps, schema_editor): + AssetBed = apps.get_model("facility", "AssetBed") + CameraPreset = apps.get_model("facility", "CameraPreset") + + paginator = Paginator( + AssetBed.objects.annotate( + row_number=Window( + expression=RowNumber(), + partition_by=[F("asset"), F("bed")], + order_by=F("id").asc(), + ) + ) + .filter(deleted=False, asset__asset_class="ONVIF") + .order_by("asset", "bed", "id"), + 1000, + ) + + for page_number in paginator.page_range: + assetbeds_to_delete = [] + presets_to_create = [] + + for asset_bed in paginator.page(page_number).object_list: + name = asset_bed.meta.get("preset_name") + + if position := asset_bed.meta.get("position"): + try: + presets_to_create.append( + CameraPreset( + name=name, + asset_bed=AssetBed.objects.filter( + asset=asset_bed.asset, bed=asset_bed.bed + ).order_by("id")[0], + position={ + "x": float(position["x"]), + "y": float(position["y"]), + "zoom": float(position["zoom"]), + }, + is_migrated=True, + ) + ) + except: + pass + if asset_bed.row_number != 1: + assetbeds_to_delete.append(asset_bed.id) + else: + assetbeds_to_delete.append(asset_bed.id) + + CameraPreset.objects.bulk_create(presets_to_create) + AssetBed.objects.filter(id__in=assetbeds_to_delete).update(deleted=True) + + operations = [ + migrations.CreateModel( + name="CameraPreset", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "external_id", + models.UUIDField(db_index=True, default=uuid.uuid4, unique=True), + ), + ( + "created_date", + models.DateTimeField(auto_now_add=True, db_index=True, null=True), + ), + ( + "modified_date", + models.DateTimeField(auto_now=True, db_index=True, null=True), + ), + ("deleted", models.BooleanField(db_index=True, default=False)), + ("name", models.CharField(max_length=255, null=True)), + ( + "position", + models.JSONField( + validators=[ + care.utils.models.validators.JSONFieldSchemaValidator( + { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": False, + "properties": { + "x": {"type": "number"}, + "y": {"type": "number"}, + "zoom": {"type": "number"}, + }, + "required": ["x", "y", "zoom"], + "type": "object", + } + ) + ], + ), + ), + ("is_migrated", models.BooleanField(default=False)), + ( + "asset_bed", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="camera_presets", + to="facility.assetbed", + ), + ), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "updated_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.RunPython( + delete_asset_beds_without_asset_class, + migrations.RunPython.noop, + ), + migrations.RunPython( + backfill_camera_presets, + migrations.RunPython.noop, + ), + migrations.AddConstraint( + model_name="assetbed", + constraint=models.UniqueConstraint( + condition=models.Q(("deleted", False)), + fields=("asset", "bed"), + name="unique_together_asset_bed", + ), + ), + ] diff --git a/care/facility/models/__init__.py b/care/facility/models/__init__.py index df41476768..d6d63cacca 100644 --- a/care/facility/models/__init__.py +++ b/care/facility/models/__init__.py @@ -4,6 +4,7 @@ from .ambulance import * # noqa from .asset import * # noqa from .bed import * # noqa +from .camera_preset import * # noqa from .daily_round import * # noqa from .encounter_symptom import * # noqa from .events import * # noqa diff --git a/care/facility/models/bed.py b/care/facility/models/bed.py index a06db2729c..992f36ac74 100644 --- a/care/facility/models/bed.py +++ b/care/facility/models/bed.py @@ -68,9 +68,22 @@ class AssetBed(BaseModel): bed = models.ForeignKey(Bed, on_delete=models.PROTECT, null=False, blank=False) meta = JSONField(default=dict, blank=True) + class Meta: + constraints = [ + models.UniqueConstraint( + name="unique_together_asset_bed", + fields=("asset", "bed"), + condition=models.Q(deleted=False), + ), + ] + def __str__(self): return f"{self.asset.name} - {self.bed.name}" + def delete(self, *args): + self.camera_presets.update(deleted=True) + return super().delete(*args) + class ConsultationBed(BaseModel): consultation = models.ForeignKey( diff --git a/care/facility/models/camera_preset.py b/care/facility/models/camera_preset.py new file mode 100644 index 0000000000..b1128f8817 --- /dev/null +++ b/care/facility/models/camera_preset.py @@ -0,0 +1,33 @@ +from django.db import models + +from care.utils.models.base import BaseModel +from care.utils.models.validators import JSONFieldSchemaValidator + +CAMERA_PRESET_POSITION_SCHEMA = { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "x": {"type": "number"}, + "y": {"type": "number"}, + "zoom": {"type": "number"}, + }, + "required": ["x", "y", "zoom"], + "additionalProperties": False, +} + + +class CameraPreset(BaseModel): + name = models.CharField(max_length=255, null=True) + asset_bed = models.ForeignKey( + "facility.AssetBed", on_delete=models.PROTECT, related_name="camera_presets" + ) + position = models.JSONField( + validators=[JSONFieldSchemaValidator(CAMERA_PRESET_POSITION_SCHEMA)] + ) + created_by = models.ForeignKey( + "users.User", null=True, blank=True, on_delete=models.PROTECT, related_name="+" + ) + updated_by = models.ForeignKey( + "users.User", null=True, blank=True, on_delete=models.PROTECT, related_name="+" + ) + is_migrated = models.BooleanField(default=False) diff --git a/care/facility/tests/test_asset_bed_api.py b/care/facility/tests/test_asset_bed_api.py new file mode 100644 index 0000000000..d22aae9bfd --- /dev/null +++ b/care/facility/tests/test_asset_bed_api.py @@ -0,0 +1,183 @@ +from rest_framework import status +from rest_framework.test import APITestCase + +from care.users.models import User +from care.utils.assetintegration.asset_classes import AssetClasses +from care.utils.tests.test_utils import TestUtils + + +class AssetBedViewSetTestCase(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( + User.TYPE_VALUE_MAP["DistrictAdmin"], + cls.district, + home_facility=cls.facility, + ) + cls.asset_location = cls.create_asset_location(cls.facility) + cls.asset = cls.create_asset(cls.asset_location) + cls.camera_asset = cls.create_asset( + cls.asset_location, asset_class=AssetClasses.ONVIF.name + ) + cls.bed = cls.create_bed(cls.facility, cls.asset_location) + + def test_link_disallowed_asset_class_asset_to_bed(self): + data = { + "asset": self.asset.external_id, + "bed": self.bed.external_id, + } + res = self.client.post("/api/v1/assetbed/", data) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + + def test_link_asset_to_bed_and_attempt_duplicate_linking(self): + data = { + "asset": self.camera_asset.external_id, + "bed": self.bed.external_id, + } + res = self.client.post("/api/v1/assetbed/", data) + self.assertEqual(res.status_code, status.HTTP_201_CREATED) + # Attempt linking same camera to the same bed again. + res = self.client.post("/api/v1/assetbed/", data) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + # List asset beds filtered by asset and bed ID and check only 1 result exists + res = self.client.get("/api/v1/assetbed/", data) + self.assertEqual(res.status_code, status.HTTP_200_OK) + self.assertEqual(res.data["count"], 1) + + +class AssetBedCameraPresetViewSetTestCase(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( + User.TYPE_VALUE_MAP["DistrictAdmin"], + cls.district, + home_facility=cls.facility, + ) + cls.asset_location = cls.create_asset_location(cls.facility) + cls.asset1 = cls.create_asset( + cls.asset_location, asset_class=AssetClasses.ONVIF.name + ) + cls.asset2 = cls.create_asset( + cls.asset_location, asset_class=AssetClasses.ONVIF.name + ) + cls.bed = cls.create_bed(cls.facility, cls.asset_location) + cls.asset_bed1 = cls.create_asset_bed(cls.asset1, cls.bed) + cls.asset_bed2 = cls.create_asset_bed(cls.asset2, cls.bed) + + def get_base_url(self, asset_bed_id=None): + return f"/api/v1/assetbed/{asset_bed_id or self.asset_bed1.external_id}/camera_presets/" + + def test_create_camera_preset_without_position(self): + res = self.client.post( + self.get_base_url(), + { + "name": "Preset without position", + "position": {}, + }, + format="json", + ) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + + def test_create_camera_preset_with_missing_required_keys_in_position(self): + res = self.client.post( + self.get_base_url(), + { + "name": "Preset with invalid position", + "position": {"key": "value"}, + }, + format="json", + ) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + + def test_create_camera_preset_with_position_not_number(self): + res = self.client.post( + self.get_base_url(), + { + "name": "Preset with invalid position", + "position": { + "x": "not a number", + "y": 1, + "zoom": 1, + }, + }, + format="json", + ) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + + def test_create_camera_preset_with_position_values_as_string(self): + res = self.client.post( + self.get_base_url(), + { + "name": "Preset with invalid position", + "position": { + "x": "1", + "y": "1", + "zoom": "1", + }, + }, + format="json", + ) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + + def test_create_camera_preset_and_presence_in_various_preset_list_apis(self): + asset_bed = self.asset_bed1 + res = self.client.post( + self.get_base_url(asset_bed.external_id), + { + "name": "Preset with proper position", + "position": { + "x": 1.0, + "y": 1.0, + "zoom": 1.0, + }, + }, + format="json", + ) + self.assertEqual(res.status_code, status.HTTP_201_CREATED) + preset_external_id = res.data["id"] + + # Check if preset in asset-bed preset list + res = self.client.get(self.get_base_url(asset_bed.external_id)) + self.assertEqual(res.status_code, status.HTTP_200_OK) + self.assertContains(res, preset_external_id) + + # Check if preset in asset preset list + res = self.client.get( + f"/api/v1/asset/{asset_bed.asset.external_id}/camera_presets/" + ) + self.assertEqual(res.status_code, status.HTTP_200_OK) + self.assertContains(res, preset_external_id) + + # Check if preset in bed preset list + res = self.client.get( + f"/api/v1/bed/{asset_bed.bed.external_id}/camera_presets/" + ) + self.assertEqual(res.status_code, status.HTTP_200_OK) + self.assertContains(res, preset_external_id) + + def test_create_camera_preset_with_same_name_in_same_bed(self): + data = { + "name": "Duplicate Preset Name", + "position": { + "x": 1.0, + "y": 1.0, + "zoom": 1.0, + }, + } + self.client.post( + self.get_base_url(self.asset_bed1.external_id), data, format="json" + ) + res = self.client.post( + self.get_base_url(self.asset_bed2.external_id), data, format="json" + ) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) diff --git a/care/utils/queryset/asset_bed.py b/care/utils/queryset/asset_bed.py new file mode 100644 index 0000000000..f9fe8f925e --- /dev/null +++ b/care/utils/queryset/asset_bed.py @@ -0,0 +1,47 @@ +from care.facility.models import Asset, AssetBed, Bed +from care.users.models import User +from care.utils.cache.cache_allowed_facilities import get_accessible_facilities + + +def get_asset_bed_queryset(user, queryset=None): + queryset = AssetBed.objects.all() if queryset is None else queryset + if user.is_superuser: + pass + elif user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: + queryset = queryset.filter(bed__facility__state=user.state) + elif user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + queryset = queryset.filter(bed__facility__district=user.district) + else: + allowed_facilities = get_accessible_facilities(user) + queryset = queryset.filter(bed__facility__id__in=allowed_facilities) + return queryset + + +def get_bed_queryset(user, queryset=None): + queryset = Bed.objects.all() if queryset is None else queryset + if user.is_superuser: + pass + elif user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: + queryset = queryset.filter(facility__state=user.state) + elif user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + queryset = queryset.filter(facility__district=user.district) + else: + allowed_facilities = get_accessible_facilities(user) + queryset = queryset.filter(facility__id__in=allowed_facilities) + return queryset + + +def get_asset_queryset(user, queryset=None): + queryset = Asset.objects.all() if queryset is None else queryset + if user.is_superuser: + pass + elif user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: + 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) + else: + allowed_facilities = get_accessible_facilities(user) + queryset = queryset.filter( + current_location__facility__id__in=allowed_facilities + ) + return queryset diff --git a/care/utils/tests/test_utils.py b/care/utils/tests/test_utils.py index 1f858c7258..91d4ac8d67 100644 --- a/care/utils/tests/test_utils.py +++ b/care/utils/tests/test_utils.py @@ -38,7 +38,7 @@ Ward, ) from care.facility.models.asset import Asset, AssetLocation -from care.facility.models.bed import Bed, ConsultationBed +from care.facility.models.bed import AssetBed, Bed, ConsultationBed from care.facility.models.facility import FacilityUser from care.facility.models.icd11_diagnosis import ( ConditionVerificationStatus, @@ -446,6 +446,12 @@ def create_bed(cls, facility: Facility, location: AssetLocation, **kwargs): data.update(kwargs) return Bed.objects.create(**data) + @classmethod + def create_asset_bed(cls, asset: Asset, bed: Bed, **kwargs): + data = {"asset": asset, "bed": bed} + data.update(kwargs) + return AssetBed.objects.create(**data) + @classmethod def create_consultation_bed( cls, diff --git a/config/api_router.py b/config/api_router.py index a2e3de78c4..b4a3aa0f4a 100644 --- a/config/api_router.py +++ b/config/api_router.py @@ -26,6 +26,10 @@ ConsultationBedViewSet, PatientAssetBedViewSet, ) +from care.facility.api.viewsets.camera_preset import ( + AssetBedCameraPresetViewSet, + CameraPresetViewSet, +) from care.facility.api.viewsets.consultation_diagnosis import ( ConsultationDiagnosisViewSet, ) @@ -223,6 +227,9 @@ router.register("asset", AssetViewSet, basename="asset") asset_nested_router = NestedSimpleRouter(router, r"asset", lookup="asset") +asset_nested_router.register( + r"camera_presets", CameraPresetViewSet, basename="asset-camera-presets" +) asset_nested_router.register( r"availability", AvailabilityViewSet, basename="asset-availability" ) @@ -234,8 +241,17 @@ router.register("asset_transaction", AssetTransactionViewSet) router.register("bed", BedViewSet, basename="bed") +bed_nested_router = NestedSimpleRouter(router, r"bed", lookup="bed") +bed_nested_router.register( + r"camera_presets", CameraPresetViewSet, basename="bed-camera-presets" +) + router.register("assetbed", AssetBedViewSet, basename="asset-bed") router.register("consultationbed", ConsultationBedViewSet, basename="consultation-bed") +assetbed_nested_router = NestedSimpleRouter(router, r"assetbed", lookup="assetbed") +assetbed_nested_router.register( + r"camera_presets", AssetBedCameraPresetViewSet, basename="assetbed-camera-presets" +) router.register("patient/search", PatientSearchViewSet, basename="patient-search") router.register("patient", PatientViewSet, basename="patient") @@ -329,6 +345,8 @@ path("", include(facility_nested_router.urls)), path("", include(facility_location_nested_router.urls)), path("", include(asset_nested_router.urls)), + path("", include(bed_nested_router.urls)), + path("", include(assetbed_nested_router.urls)), path("", include(patient_nested_router.urls)), path("", include(patient_notes_nested_router.urls)), path("", include(consultation_nested_router.urls)),