Skip to content

Commit

Permalink
Merge branch 'lock-asset-feature' into qa-testing
Browse files Browse the repository at this point in the history
  • Loading branch information
JahnabDutta committed Sep 5, 2023
2 parents 1964c5a + 01c15a2 commit 9950a6c
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 11 deletions.
24 changes: 21 additions & 3 deletions care/facility/api/viewsets/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
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.exceptions import APIException, PermissionDenied, ValidationError
from rest_framework.mixins import (
CreateModelMixin,
DestroyModelMixin,
Expand Down Expand Up @@ -271,10 +271,28 @@ def operate_assets(self, request, *args, **kwargs):
"middleware_hostname": asset.current_location.facility.middleware_address,
}
)

asset_class.validate_action(action)
result = asset_class.handle_action(action)
result = asset_class.handle_action(
action,
{
"username": request.user.username,
"asset_id": asset.external_id,
},
)
return Response({"result": result}, status=status.HTTP_200_OK)

except PermissionDenied as e:
return Response(
{
"message": e.detail.get("message", None),
"username": e.detail.get("username", None),
"firstName": e.detail.get("firstName", None),
"lastName": e.detail.get("lastName", None),
"role": e.detail.get("role", None),
"homeFacility": e.detail.get("homeFacility", None),
},
status=status.HTTP_409_CONFLICT,
)
except ValidationError as e:
return Response({"message": e.detail}, status=status.HTTP_400_BAD_REQUEST)

Expand Down
1 change: 1 addition & 0 deletions care/facility/models/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class Medium(enum.Enum):

class Event(enum.Enum):
MESSAGE = 0
ASSET_UNLOCKED = 10
PATIENT_CREATED = 20
PATIENT_UPDATED = 30
PATIENT_DELETED = 40
Expand Down
76 changes: 75 additions & 1 deletion care/facility/tests/test_asset_operate_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from rest_framework.test import APIRequestFactory, APITestCase

from care.facility.api.viewsets.asset import AssetViewSet
from care.facility.models import Asset, AssetBed, AssetLocation, Bed
from care.facility.models import Asset, AssetBed, AssetLocation, Bed, FacilityUser
from care.facility.tests.mixins import TestClassMixin
from care.utils.tests.test_base import TestBase

Expand All @@ -15,7 +15,11 @@ def setUp(self):
state = self.create_state()
district = self.create_district(state=state)
self.user = self.create_user(district=district, username="test user")
self.user_2 = self.create_user(district=district, username="test user 2")
facility = self.create_facility(district=district, user=self.user)
self.facility_user = FacilityUser.objects.create(
user=self.user_2, facility=facility, created_by=self.user
)
self.asset1_location = AssetLocation.objects.create(
name="asset1 location", location_type=1, facility=facility
)
Expand Down Expand Up @@ -124,3 +128,73 @@ def test_ventilator(self):
self.asset.meta = self.ventilator_meta
self.asset.save()
pass

def test_lock_asset(self):
self.asset.asset_class = "ONVIF"
self.asset.meta = self.onvif_meta
self.asset.save()
sample_data_lock = {
"action": {
"type": "lock_asset",
"data": {},
}
}
sample_data_unlock = {
"action": {
"type": "unlock_asset",
"data": {},
}
}
response = self.new_request(
(
f"/api/v1/asset/{self.asset.external_id}/operate_assets/",
sample_data_lock,
"json",
),
{"post": "operate_assets"},
AssetViewSet,
self.user,
{"external_id": self.asset.external_id},
)

self.assertEqual(response.status_code, status.HTTP_200_OK)

response = self.new_request(
(
f"/api/v1/asset/{self.asset.external_id}/operate_assets/",
sample_data_unlock,
"json",
),
{"post": "operate_assets"},
AssetViewSet,
self.user_2,
{"external_id": self.asset.external_id},
)
self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)

response = self.new_request(
(
f"/api/v1/asset/{self.asset.external_id}/operate_assets/",
sample_data_unlock,
"json",
),
{"post": "operate_assets"},
AssetViewSet,
self.user,
{"external_id": self.asset.external_id},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)

response = self.new_request(
(
f"/api/v1/asset/{self.asset.external_id}/operate_assets/",
sample_data_lock,
"json",
),
{"post": "operate_assets"},
AssetViewSet,
self.user_2,
{"external_id": self.asset.external_id},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.asset.delete()
123 changes: 122 additions & 1 deletion care/utils/assetintegration/base.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import enum
import json

import requests
from django.conf import settings
from rest_framework.exceptions import APIException
from django.core.cache import cache
from rest_framework.exceptions import APIException, PermissionDenied

from care.users.models import User
from care.utils.jwks.token_generator import generate_jwt


class BaseAssetIntegration:
auth_header_type = "Care_Bearer "

class BaseAssetActions(enum.Enum):
UNLOCK_ASSET = "unlock_asset"
LOCK_ASSET = "lock_asset"
REQUEST_ACCESS = "request_access"

def __init__(self, meta):
self.meta = meta
self.host = self.meta["local_ip_address"]
Expand Down Expand Up @@ -55,3 +63,116 @@ def api_get(self, url, data=None):

def validate_action(self, action):
pass

def generate_system_users(self, asset_id):
asset_queue_key = f"waiting_queue_{asset_id}"
if cache.get(asset_queue_key) is None:
return []
else:
queue = cache.get(asset_queue_key)
users_array = []
for user in queue:
users_array.append(User.objects.get(username=user))
return users_array

def generate_notification(self, asset_id):
from care.utils.notification_handler import send_webpush

message = {
"type": "MESSAGE",
"asset_id": str(asset_id),
"status": "success",
}
user_array = self.generate_system_users(asset_id)
for username in user_array:
send_webpush(username=username, message=json.dumps(message))

def add_to_waiting_queue(self, username, asset_id):
asset_queue_key = f"waiting_queue_{asset_id}"
if cache.get(asset_queue_key) is None:
cache.set(asset_queue_key, [username], timeout=None)
else:
queue = cache.get(asset_queue_key)
if username not in queue:
queue.append(username)
cache.set(asset_queue_key, queue, timeout=None)

def remove_from_waiting_queue(self, username, asset_id):
asset_queue_key = f"waiting_queue_{asset_id}"
if cache.get(asset_queue_key) is None:
return
else:
queue = cache.get(asset_queue_key)
if username in queue:
queue = [x for x in queue if x != username]
cache.set(asset_queue_key, queue, timeout=None)

def unlock_asset(self, username, asset_id):
if cache.get(asset_id) is None:
self.remove_from_waiting_queue(username, asset_id)
self.generate_notification(asset_id)
return True
elif cache.get(asset_id) == username:
cache.delete(asset_id)
self.remove_from_waiting_queue(username, asset_id)
self.generate_notification(asset_id)
return True
elif cache.get(asset_id) != username:
self.remove_from_waiting_queue(username, asset_id)
return False
return True

def lock_asset(self, username, asset_id):
if cache.get(asset_id) is None or not cache.get(asset_id):
cache.set(asset_id, username, timeout=None)
self.remove_from_waiting_queue(username, asset_id)
return True
elif cache.get(asset_id) == username:
self.remove_from_waiting_queue(username, asset_id)
return True
self.add_to_waiting_queue(username, asset_id)
return False

def raise_conflict(self, asset_id):
user: User = User.objects.get(username=cache.get(asset_id))
raise PermissionDenied(
{
"message": "Asset is currently in use by another user",
"username": user.username,
"firstName": user.first_name,
"lastName": user.last_name,
"role": [x for x in User.TYPE_CHOICES if x[0] == user.user_type][0][1],
"homeFacility": user.home_facility.name
if (user.home_facility and user.home_facility.name)
else "",
}
)

def verify_access(self, username, asset_id):
if cache.get(asset_id) is None or cache.get(asset_id) == username:
return True
elif cache.get(asset_id) != username:
return False
return True

def request_access(self, username, asset_id):
from care.utils.notification_handler import send_webpush

if cache.get(asset_id) is None or cache.get(asset_id) == username:
return {}
elif cache.get(asset_id) != username:
user: User = User.objects.get(username=username)
message = {
"type": "MESSAGE",
"status": "request",
"username": user.username,
"firstName": user.first_name,
"lastName": user.last_name,
"role": [x for x in User.TYPE_CHOICES if x[0] == user.user_type][0][1],
"homeFacility": user.home_facility.name
if (user.home_facility and user.home_facility.name)
else "",
}

send_webpush(username=cache.get(asset_id), message=json.dumps(message))
return {"message": "user notified"}
2 changes: 1 addition & 1 deletion care/utils/assetintegration/hl7monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def __init__(self, meta):
dict((key, f"{key} not found in asset metadata") for key in e.args)
)

def handle_action(self, action):
def handle_action(self, action, verifcation_data: dict = None):
action_type = action["type"]

if action_type == self.HL7MonitorActions.GET_VITALS.value:
Expand Down
33 changes: 29 additions & 4 deletions care/utils/assetintegration/onvif.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import enum

from django.core.cache import cache
from rest_framework.exceptions import ValidationError

from care.utils.assetintegration.base import BaseAssetIntegration
Expand All @@ -26,13 +27,16 @@ def __init__(self, meta):
dict((key, f"{key} not found in asset metadata") for key in e.args)
)

def handle_action(self, action):
def handle_action(self, action, verifcation_data: dict = None):
action_type = action["type"]
action_data = action.get("data", {})
allowed_action_data = ["x", "y", "zoom"]
action_data = {
key: action_data[key] for key in action_data if key in allowed_action_data
}

username = verifcation_data.get("username", None)
asset_id = verifcation_data.get("asset_id", None)
request_body = {
"hostname": self.host,
"port": 80,
Expand All @@ -42,20 +46,41 @@ def handle_action(self, action):
**action_data,
}

print(cache.get(f"waiting_queue_{asset_id}"))

if action_type == BaseAssetIntegration.BaseAssetActions.REQUEST_ACCESS.value:
return self.request_access(username, asset_id)

if action_type == BaseAssetIntegration.BaseAssetActions.UNLOCK_ASSET.value:
if self.unlock_asset(username, asset_id):
return {"message": "Asset Unlocked"}
self.raise_conflict(asset_id=asset_id)

if action_type == BaseAssetIntegration.BaseAssetActions.LOCK_ASSET.value:
if self.lock_asset(username, asset_id):
return {"message": "Asset Locked"}
self.raise_conflict(asset_id=asset_id)

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 action_type == self.OnvifActions.GOTO_PRESET.value:
return self.api_post(self.get_url("gotoPreset"), request_body)
if self.verify_access(username, asset_id):
return self.api_post(self.get_url("gotoPreset"), request_body)
self.raise_conflict(asset_id=asset_id)

if action_type == self.OnvifActions.ABSOLUTE_MOVE.value:
return self.api_post(self.get_url("absoluteMove"), request_body)
if self.verify_access(username, asset_id):
return self.api_post(self.get_url("absoluteMove"), request_body)
self.raise_conflict(asset_id=asset_id)

if action_type == self.OnvifActions.RELATIVE_MOVE.value:
return self.api_post(self.get_url("relativeMove"), request_body)
if self.verify_access(username, asset_id):
return self.api_post(self.get_url("relativeMove"), request_body)
self.raise_conflict(asset_id=asset_id)

raise ValidationError({"action": "invalid action type"})

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

def handle_action(self, action):
def handle_action(self, action, verifcation_data: dict = None):
action_type = action["type"]

if action_type == self.VentilatorActions.GET_VITALS.value:
Expand Down
6 changes: 6 additions & 0 deletions care/utils/notification_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.conf import settings
from pywebpush import WebPushException, webpush

from care.facility.models.asset import Asset
from care.facility.models.daily_round import DailyRound
from care.facility.models.facility import Facility, FacilityUser
from care.facility.models.notification import Notification
Expand Down Expand Up @@ -230,6 +231,11 @@ def generate_system_message(self):
self.caused_object.patient.name,
self.caused_by.get_full_name(),
)
elif isinstance(self.caused_object, Asset):
if self.event == Notification.Event.ASSET_UNLOCKED.value:
message = "{} is ready to use".format(
self.caused_object.name,
)
return message

def generate_sms_message(self):
Expand Down

0 comments on commit 9950a6c

Please sign in to comment.