Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Privacy consultation bed #2506

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions care/facility/api/viewsets/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from rest_framework import exceptions, status
from rest_framework import filters as drf_filters
from rest_framework.decorators import action
from rest_framework.exceptions import APIException, ValidationError
from rest_framework.exceptions import APIException, PermissionDenied, ValidationError
from rest_framework.mixins import (
CreateModelMixin,
DestroyModelMixin,
Expand Down Expand Up @@ -395,12 +395,18 @@
"middleware_hostname": middleware_hostname,
}
)
result = asset_class.handle_action(action)
result = asset_class.handle_action(action, request.user)

Check warning on line 398 in care/facility/api/viewsets/asset.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/viewsets/asset.py#L398

Added line #L398 was not covered by tests
return Response({"result": result}, status=status.HTTP_200_OK)

except ValidationError as e:
return Response({"detail": e.detail}, status=status.HTTP_400_BAD_REQUEST)

except PermissionDenied as e:
return Response(

Check warning on line 405 in care/facility/api/viewsets/asset.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/viewsets/asset.py#L405

Added line #L405 was not covered by tests
{**e.detail},
status=status.HTTP_409_CONFLICT,
khavinshankar marked this conversation as resolved.
Show resolved Hide resolved
)

except KeyError as e:
return Response(
{"message": {key: "is required" for key in e.args}},
Expand Down
25 changes: 25 additions & 0 deletions care/facility/api/viewsets/bed.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import filters as drf_filters
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
from rest_framework.exceptions import ValidationError as DRFValidationError
from rest_framework.fields import get_error_detail
Expand Down Expand Up @@ -264,3 +265,27 @@
allowed_facilities = get_accessible_facilities(user)
queryset = queryset.filter(bed__facility__id__in=allowed_facilities)
return queryset

@action(detail=True, methods=["patch"])
def set_privacy(self, request, external_id):
consultation_bed: ConsultationBed = self.get_object()

Check warning on line 271 in care/facility/api/viewsets/bed.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/viewsets/bed.py#L271

Added line #L271 was not covered by tests

is_privacy_enabled = request.data.get("is_privacy_enabled")

Check warning on line 273 in care/facility/api/viewsets/bed.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/viewsets/bed.py#L273

Added line #L273 was not covered by tests

if is_privacy_enabled is None:
khavinshankar marked this conversation as resolved.
Show resolved Hide resolved
return Response(

Check warning on line 276 in care/facility/api/viewsets/bed.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/viewsets/bed.py#L276

Added line #L276 was not covered by tests
{"detail": "'is_privacy_enabled' field is required."},
status=status.HTTP_400_BAD_REQUEST,
)

if not consultation_bed.consultation.patient.has_object_update_permission(
khavinshankar marked this conversation as resolved.
Show resolved Hide resolved
request
):
raise PermissionDenied

Check warning on line 284 in care/facility/api/viewsets/bed.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/viewsets/bed.py#L284

Added line #L284 was not covered by tests

consultation_bed.is_privacy_enabled = is_privacy_enabled
consultation_bed.save(update_fields=["is_privacy_enabled"])

Check warning on line 287 in care/facility/api/viewsets/bed.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/viewsets/bed.py#L286-L287

Added lines #L286 - L287 were not covered by tests

return Response(

Check warning on line 289 in care/facility/api/viewsets/bed.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/viewsets/bed.py#L289

Added line #L289 was not covered by tests
ConsultationBedSerializer(consultation_bed).data, status=status.HTTP_200_OK
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.1.1 on 2024-09-28 17:40

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('facility', '0465_merge_20240923_1045'),
]

operations = [
migrations.AddField(
model_name='consultationbed',
name='is_privacy_enabled',
field=models.BooleanField(default=False),
),
]
1 change: 1 addition & 0 deletions care/facility/models/bed.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class ConsultationBed(BaseModel):
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)
is_privacy_enabled = models.BooleanField(default=False)
meta = JSONField(default=dict, blank=True)
assets = models.ManyToManyField(
Asset, through="ConsultationBedAsset", related_name="assigned_consultation_beds"
Expand Down
2 changes: 1 addition & 1 deletion care/utils/assetintegration/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def __init__(self, meta):
self.insecure_connection = self.meta.get("insecure_connection", False)
self.timeout = settings.MIDDLEWARE_REQUEST_TIMEOUT

def handle_action(self, action):
def handle_action(self, action, user):
pass

def get_url(self, endpoint):
Expand Down
2 changes: 1 addition & 1 deletion care/utils/assetintegration/hl7monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def __init__(self, meta):
{key: f"{key} not found in asset metadata" for key in e.args}
) from e

def handle_action(self, action):
def handle_action(self, action, user):
action_type = action["type"]

if action_type == self.HL7MonitorActions.GET_VITALS.value:
Expand Down
65 changes: 55 additions & 10 deletions care/utils/assetintegration/onvif.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import enum

from rest_framework.exceptions import ValidationError
from rest_framework.exceptions import PermissionDenied, ValidationError

from care.utils.assetintegration.base import BaseAssetIntegration
from care.utils.assetintegration.usage_manager import UsageManager


class OnvifAsset(BaseAssetIntegration):
Expand All @@ -16,6 +17,11 @@
RELATIVE_MOVE = "relative_move"
GET_STREAM_TOKEN = "get_stream_token"

LOCK_CAMERA = "lock_camera"
UNLOCK_CAMERA = "unlock_camera"
REQUEST_ACCESS = "request_access"
TAKE_CONTROL = "take_control"

def __init__(self, meta):
try:
super().__init__(meta)
Expand All @@ -27,9 +33,10 @@
{key: f"{key} not found in asset metadata" for key in e.args}
) from e

def handle_action(self, action):
def handle_action(self, action, user): # noqa: PLR0911
action_type = action["type"]
action_data = action.get("data", {})
camera_manager = UsageManager(self.id, user)

Check warning on line 39 in care/utils/assetintegration/onvif.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/onvif.py#L39

Added line #L39 was not covered by tests

request_body = {
"hostname": self.host,
Expand All @@ -40,12 +47,58 @@
**action_data,
}

if action_type == self.OnvifActions.LOCK_CAMERA.value:
if camera_manager.lock_camera():
return {

Check warning on line 52 in care/utils/assetintegration/onvif.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/onvif.py#L52

Added line #L52 was not covered by tests
"message": "You now have access to the camera controls, the camera is locked for other users",
"camera_user": camera_manager.current_user(),
}

raise PermissionDenied(

Check warning on line 57 in care/utils/assetintegration/onvif.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/onvif.py#L57

Added line #L57 was not covered by tests
{
"message": "Camera is currently in used by another user, you have been added to the waiting list for camera controls access",
"camera_user": camera_manager.current_user(),
}
)

if action_type == self.OnvifActions.UNLOCK_CAMERA.value:
camera_manager.unlock_camera()
return {"message": "Camera controls unlocked"}

Check warning on line 66 in care/utils/assetintegration/onvif.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/onvif.py#L65-L66

Added lines #L65 - L66 were not covered by tests

if action_type == self.OnvifActions.REQUEST_ACCESS.value:
if camera_manager.request_access():
return {

Check warning on line 70 in care/utils/assetintegration/onvif.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/onvif.py#L70

Added line #L70 was not covered by tests
"message": "Access to camera camera controls granted",
"camera_user": camera_manager.current_user(),
}

return {

Check warning on line 75 in care/utils/assetintegration/onvif.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/onvif.py#L75

Added line #L75 was not covered by tests
"message": "Requested access to camera controls, waiting for current user to release",
"camera_user": camera_manager.current_user(),
}

if action_type == self.OnvifActions.GET_STREAM_TOKEN.value:
return self.api_post(

Check warning on line 81 in care/utils/assetintegration/onvif.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/onvif.py#L81

Added line #L81 was not covered by tests
self.get_url("api/stream/getToken/videoFeed"),
{
"stream_id": self.access_key,
},
)

if action_type == self.OnvifActions.GET_CAMERA_STATUS.value:
return self.api_get(self.get_url("status"), request_body)

if action_type == self.OnvifActions.GET_PRESETS.value:
return self.api_get(self.get_url("presets"), request_body)

if not camera_manager.has_access():
raise PermissionDenied(

Check warning on line 95 in care/utils/assetintegration/onvif.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/onvif.py#L95

Added line #L95 was not covered by tests
{
"message": "Camera is currently in used by another user, you have been added to the waiting list for camera controls access",
"camera_user": camera_manager.current_user(),
}
)
sainak marked this conversation as resolved.
Show resolved Hide resolved

if action_type == self.OnvifActions.GOTO_PRESET.value:
return self.api_post(self.get_url("gotoPreset"), request_body)

Expand All @@ -55,12 +108,4 @@
if action_type == self.OnvifActions.RELATIVE_MOVE.value:
return self.api_post(self.get_url("relativeMove"), request_body)

if action_type == self.OnvifActions.GET_STREAM_TOKEN.value:
return self.api_post(
self.get_url("api/stream/getToken/videoFeed"),
{
"stream_id": self.access_key,
},
)

raise ValidationError({"action": "invalid action type"})
132 changes: 132 additions & 0 deletions care/utils/assetintegration/usage_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import json

from django.core.cache import cache

from care.users.models import User


class UsageManager:
def __init__(self, asset_id: str, user: User):
self.asset = str(asset_id)
self.user = user
self.waiting_list_cache_key = f"onvif_waiting_list_{asset_id}"
khavinshankar marked this conversation as resolved.
Show resolved Hide resolved
self.current_user_cache_key = f"onvif_current_user_{asset_id}"

Check warning on line 13 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L10-L13

Added lines #L10 - L13 were not covered by tests

def get_waiting_list(self) -> list[User]:
asset_queue = cache.get(self.waiting_list_cache_key)

Check warning on line 16 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L16

Added line #L16 was not covered by tests

return list(User.objects.filter(username__in=asset_queue or []))

Check warning on line 18 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L18

Added line #L18 was not covered by tests

def add_to_waiting_list(self) -> int:
asset_queue = cache.get(self.waiting_list_cache_key)

Check warning on line 21 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L21

Added line #L21 was not covered by tests
khavinshankar marked this conversation as resolved.
Show resolved Hide resolved

if asset_queue is None:
asset_queue = []

Check warning on line 24 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L24

Added line #L24 was not covered by tests

if self.user.username not in asset_queue:
asset_queue.append(self.user.username)
khavinshankar marked this conversation as resolved.
Show resolved Hide resolved
cache.set(self.waiting_list_cache_key, asset_queue)

Check warning on line 28 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L27-L28

Added lines #L27 - L28 were not covered by tests

return len(asset_queue)

Check warning on line 30 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L30

Added line #L30 was not covered by tests

def remove_from_waiting_list(self) -> None:
asset_queue = cache.get(self.waiting_list_cache_key)

Check warning on line 33 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L33

Added line #L33 was not covered by tests

if asset_queue is None:
asset_queue = []

Check warning on line 36 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L36

Added line #L36 was not covered by tests

if self.user.username in asset_queue:
asset_queue.remove(self.user.username)
cache.set(self.waiting_list_cache_key, asset_queue)

Check warning on line 40 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L39-L40

Added lines #L39 - L40 were not covered by tests

def clear_waiting_list(self) -> None:
cache.delete(self.waiting_list_cache_key)

Check warning on line 43 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L43

Added line #L43 was not covered by tests

def current_user(self) -> dict:
from care.facility.api.serializers.asset import UserBaseMinimumSerializer

Check warning on line 46 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L46

Added line #L46 was not covered by tests

current_user = cache.get(self.current_user_cache_key)

Check warning on line 48 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L48

Added line #L48 was not covered by tests

if current_user is None:
return None

Check warning on line 51 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L51

Added line #L51 was not covered by tests

user = User.objects.filter(username=current_user).first()

Check warning on line 53 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L53

Added line #L53 was not covered by tests

if user is None:
cache.delete(self.current_user_cache_key)
return None

Check warning on line 57 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L56-L57

Added lines #L56 - L57 were not covered by tests

return UserBaseMinimumSerializer(user).data

Check warning on line 59 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L59

Added line #L59 was not covered by tests

def has_access(self) -> bool:
current_user = cache.get(self.current_user_cache_key)
return current_user is None or current_user == self.user.username

Check warning on line 63 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L62-L63

Added lines #L62 - L63 were not covered by tests

def notify_waiting_list_on_asset_availabe(self) -> None:
from care.utils.notification_handler import send_webpush

Check warning on line 66 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L66

Added line #L66 was not covered by tests

message = json.dumps(

Check warning on line 68 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L68

Added line #L68 was not covered by tests
{
"type": "MESSAGE",
"asset_id": self.asset,
"message": "Camera is now available",
"action": "CAMERA_AVAILABILITY",
}
)

for user in self.get_waiting_list():
send_webpush(username=user.username, message=message)

Check warning on line 78 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L78

Added line #L78 was not covered by tests

def notify_current_user_on_request_access(self) -> None:
from care.utils.notification_handler import send_webpush

Check warning on line 81 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L81

Added line #L81 was not covered by tests

current_user = cache.get(self.current_user_cache_key)

Check warning on line 83 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L83

Added line #L83 was not covered by tests

if current_user is None:
return

Check warning on line 86 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L86

Added line #L86 was not covered by tests

requester = User.objects.filter(username=self.user.username).first()

Check warning on line 88 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L88

Added line #L88 was not covered by tests

if requester is None:
return

Check warning on line 91 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L91

Added line #L91 was not covered by tests

message = json.dumps(

Check warning on line 93 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L93

Added line #L93 was not covered by tests
{
"type": "MESSAGE",
"asset_id": self.asset,
"message": f"{User.REVERSE_TYPE_MAP[requester.user_type]}, {requester.full_name} ({requester.username}) has requested access to the camera",
"action": "CAMERA_ACCESS_REQUEST",
}
)

send_webpush(username=current_user, message=message)

Check warning on line 102 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L102

Added line #L102 was not covered by tests

def lock_camera(self) -> bool:
current_user = cache.get(self.current_user_cache_key)

Check warning on line 105 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L105

Added line #L105 was not covered by tests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not how we implement locks in care, refer to other lock implementations

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vigneshhari This implementation was based on the existing implementation of locks in discharge summary (https://github.com/ohcnetwork/care/blob/develop/care/facility/utils/reports/discharge_summary.py#L40)


if current_user is None or current_user == self.user.username:
cache.set(self.current_user_cache_key, self.user.username)
self.remove_from_waiting_list()
return True

Check warning on line 110 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L108-L110

Added lines #L108 - L110 were not covered by tests

self.add_to_waiting_list()
return False

Check warning on line 113 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L112-L113

Added lines #L112 - L113 were not covered by tests

def unlock_camera(self) -> None:
current_user = cache.get(self.current_user_cache_key)

Check warning on line 116 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L116

Added line #L116 was not covered by tests

if current_user == self.user.username:
cache.delete(self.current_user_cache_key)
self.notify_waiting_list_on_asset_availabe()

Check warning on line 120 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L119-L120

Added lines #L119 - L120 were not covered by tests

self.remove_from_waiting_list()

Check warning on line 122 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L122

Added line #L122 was not covered by tests

def request_access(self) -> bool:
if self.lock_camera():
return True

Check warning on line 126 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L126

Added line #L126 was not covered by tests

self.notify_current_user_on_request_access()
return False

Check warning on line 129 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L128-L129

Added lines #L128 - L129 were not covered by tests

def take_control(self):
pass

Check warning on line 132 in care/utils/assetintegration/usage_manager.py

View check run for this annotation

Codecov / codecov/patch

care/utils/assetintegration/usage_manager.py#L132

Added line #L132 was not covered by tests
2 changes: 1 addition & 1 deletion care/utils/assetintegration/ventilator.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def __init__(self, meta):
{key: f"{key} not found in asset metadata" for key in e.args}
) from e

def handle_action(self, action):
def handle_action(self, action, user):
action_type = action["type"]

if action_type == self.VentilatorActions.GET_VITALS.value:
Expand Down
Loading