Skip to content

Commit

Permalink
Add nurse user type (#1819)
Browse files Browse the repository at this point in the history
* add nurse user type

* migrate staff users to nurse role

* add update permissions checks

* rebase migrations

* fix issues

* move permission checks out of get_queryset

* fix file upload post

* restrict nurse users from accessing external results
  • Loading branch information
sainak authored Jan 11, 2024
1 parent 2133846 commit a92b60a
Show file tree
Hide file tree
Showing 29 changed files with 316 additions and 296 deletions.
28 changes: 14 additions & 14 deletions care/facility/api/viewsets/bed.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ class BedViewSet(
search_fields = ["name"]
filterset_class = BedFilter

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(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 create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
Expand All @@ -83,20 +97,6 @@ def create(self, request, *args, **kwargs):
serializer.data, status=status.HTTP_201_CREATED, headers=headers
)

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(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 destroy(self, request, *args, **kwargs):
if request.user.user_type < User.TYPE_VALUE_MAP["DistrictLabAdmin"]:
raise PermissionDenied()
Expand Down
28 changes: 26 additions & 2 deletions care/facility/api/viewsets/file_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
RetrieveModelMixin,
UpdateModelMixin,
)
from rest_framework.permissions import IsAuthenticated
from rest_framework.permissions import BasePermission, IsAuthenticated
from rest_framework.viewsets import GenericViewSet

from care.facility.api.serializers.file_upload import (
Expand All @@ -18,13 +18,36 @@
check_permissions,
)
from care.facility.models.file_upload import FileUpload
from care.users.models import User


class FileUploadFilter(filters.FilterSet):
file_category = filters.CharFilter(field_name="file_category")
is_archived = filters.BooleanFilter(field_name="is_archived")


class FileUploadPermission(BasePermission):
def has_permission(self, request, view) -> bool:
if request.user.user_type in (
User.TYPE_VALUE_MAP["StaffReadOnly"],
User.TYPE_VALUE_MAP["Staff"],
):
if request.method == "GET":
return request.query_params.get("file_type") not in (
"PATIENT",
"CONSULTATION",
)
else:
return request.data.get("file_type") not in (
"PATIENT",
"CONSULTATION",
)
return True

def has_object_permission(self, request, view, obj) -> bool:
return self.has_permission(request, view)


class FileUploadViewSet(
CreateModelMixin,
RetrieveModelMixin,
Expand All @@ -36,7 +59,7 @@ class FileUploadViewSet(
queryset = (
FileUpload.objects.all().select_related("uploaded_by").order_by("-created_date")
)
permission_classes = [IsAuthenticated]
permission_classes = [IsAuthenticated, FileUploadPermission]
lookup_field = "external_id"
filter_backends = (filters.DjangoFilterBackend,)
filterset_class = FileUploadFilter
Expand All @@ -54,6 +77,7 @@ def get_serializer_class(self):
def get_queryset(self):
if "file_type" not in self.request.GET:
raise ValidationError({"file_type": "file_type missing in request params"})

if "associating_id" not in self.request.GET:
raise ValidationError(
{"associating_id": "associating_id missing in request params"}
Expand Down
12 changes: 8 additions & 4 deletions care/facility/api/viewsets/hospital_doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from care.facility.api.viewsets import FacilityBaseViewset
from care.facility.models import Facility, HospitalDoctors
from care.users.models import User
from care.utils.cache.cache_allowed_facilities import get_accessible_facilities


class HospitalDoctorViewSet(FacilityBaseViewset, ListModelMixin):
Expand All @@ -24,12 +25,15 @@ def get_queryset(self):
facility__external_id=self.kwargs.get("facility_external_id")
)
if user.is_superuser:
return queryset
pass
elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]:
return queryset.filter(facility__state=user.state)
queryset = queryset.filter(facility__state=user.state)
elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]:
return queryset.filter(facility__district=user.district)
return queryset.filter(facility__users__id__exact=user.id)
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_object(self):
return get_object_or_404(self.get_queryset(), area=self.kwargs.get("pk"))
Expand Down
5 changes: 4 additions & 1 deletion care/facility/api/viewsets/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from drf_spectacular.utils import extend_schema, inline_serializer
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.generics import get_object_or_404
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin
from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly
Expand All @@ -13,6 +13,7 @@

from care.facility.api.serializers.notification import NotificationSerializer
from care.facility.models.notification import Notification
from care.users.models import User
from care.utils.filters.choicefilter import CareChoiceFilter, inverse_choices
from care.utils.notification_handler import NotificationGenerator
from care.utils.queryset.facility import get_facility_queryset
Expand Down Expand Up @@ -67,6 +68,8 @@ def public_key(self, request, *args, **kwargs):
@action(detail=False, methods=["POST"])
def notify(self, request, *args, **kwargs):
user = request.user
if user.user_type < User.TYPE_VALUE_MAP["Doctor"]:
raise PermissionDenied()
if "facility" not in request.data or request.data["facility"] == "":
raise ValidationError({"facility": "is required"})
if "message" not in request.data or request.data["message"] == "":
Expand Down
17 changes: 15 additions & 2 deletions care/facility/api/viewsets/patient_external_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
UpdateModelMixin,
)
from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
from rest_framework.permissions import IsAuthenticated
from rest_framework.permissions import BasePermission, IsAuthenticated
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet

Expand Down Expand Up @@ -64,6 +64,19 @@ class PatientExternalTestFilter(filters.FilterSet):
created_date = DateFromToRangeFilter(field_name="created_date")


class PatientExternalTestPermission(BasePermission):
def has_permission(self, request, view):
return request.user.user_type not in (
User.TYPE_VALUE_MAP["StaffReadOnly"],
User.TYPE_VALUE_MAP["Staff"],
User.TYPE_VALUE_MAP["NurseReadOnly"],
User.TYPE_VALUE_MAP["Nurse"],
)

def has_object_permission(self, request, view, obj):
return self.has_permission(request, view)


class PatientExternalTestViewSet(
RetrieveModelMixin,
ListModelMixin,
Expand All @@ -77,7 +90,7 @@ class PatientExternalTestViewSet(
.all()
.order_by("-id")
)
permission_classes = (IsAuthenticated,)
permission_classes = (IsAuthenticated, PatientExternalTestPermission)
filter_backends = (filters.DjangoFilterBackend,)
filterset_class = PatientExternalTestFilter
parser_classes = (MultiPartParser, FormParser, JSONParser)
Expand Down
8 changes: 7 additions & 1 deletion care/facility/api/viewsets/patient_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from dry_rest_permissions.generics import DRYPermissionFiltersBase, DRYPermissions
from rest_framework import mixins, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

Expand Down Expand Up @@ -117,6 +117,12 @@ def list(self, request, *args, **kwargs):
- district - District ID
- district_name - District name - case insensitive match
"""
if (
not self.kwargs.get("patient_external_id")
and request.user.user_type < User.TYPE_VALUE_MAP["Doctor"]
):
raise PermissionDenied()

if settings.CSV_REQUEST_PARAMETER in request.GET:
queryset = self.filter_queryset(self.get_queryset()).values(
*PatientSample.CSV_MAPPING.keys()
Expand Down
2 changes: 1 addition & 1 deletion care/facility/api/viewsets/shifting.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ class ShifitngRequestCommentViewSet(
lookup_field = "external_id"
queryset = ShiftingRequestComment.objects.all().order_by("-created_date")

permission_classes = (IsAuthenticated,)
permission_classes = (IsAuthenticated, DRYPermissions)

def get_queryset(self):
queryset = self.queryset.filter(
Expand Down
2 changes: 1 addition & 1 deletion care/facility/migrations/0001_initial_squashed.py
Original file line number Diff line number Diff line change
Expand Up @@ -825,7 +825,7 @@ class Migration(migrations.Migration):
},
bases=(
models.Model,
care.facility.models.mixins.permissions.asset.AssetsPermissionMixin,
# care.facility.models.mixins.permissions.asset.AssetsPermissionMixin,
),
),
migrations.CreateModel(
Expand Down
6 changes: 4 additions & 2 deletions care/facility/models/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
from care.facility.models import reverse_choices
from care.facility.models.facility import Facility
from care.facility.models.json_schema.asset import ASSET_META
from care.facility.models.mixins.permissions.asset import AssetsPermissionMixin
from care.facility.models.mixins.permissions.facility import (
FacilityRelatedPermissionMixin,
)
from care.users.models import User
from care.utils.assetintegration.asset_classes import AssetClasses
from care.utils.models.base import BaseModel
Expand All @@ -25,7 +27,7 @@ class AvailabilityStatus(models.TextChoices):
UNDER_MAINTENANCE = "Under Maintenance"


class AssetLocation(BaseModel, AssetsPermissionMixin):
class AssetLocation(BaseModel, FacilityRelatedPermissionMixin):
"""
This model is also used to store rooms that the assets are in, Since these rooms are mapped to
actual rooms in the hospital, Beds are also connected to this model to remove duplication of efforts
Expand Down
7 changes: 0 additions & 7 deletions care/facility/models/base.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
from care.users.models import User
from care.utils.models.base import BaseModel

READ_ONLY_USER_TYPES = [
User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"],
User.TYPE_VALUE_MAP["StateReadOnlyAdmin"],
User.TYPE_VALUE_MAP["StaffReadOnly"],
]


def pretty_boolean(val, a="YES", b="NO", c="Not Specified"):
if val is None:
Expand Down
61 changes: 19 additions & 42 deletions care/facility/models/daily_round.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,19 +519,11 @@ def save(self, *args, **kwargs):

super(DailyRound, self).save(*args, **kwargs)

@staticmethod
def has_write_permission(request):
if "/analyse" not in request.get_full_path():
if (
request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"]
or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"]
or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"]
):
return False
return DailyRound.has_read_permission(request)

@staticmethod
def has_read_permission(request):
if request.user.user_type < User.TYPE_VALUE_MAP["NurseReadOnly"]:
return False

consultation = get_object_or_404(
PatientConsultation,
external_id=request.parser_context["kwargs"]["consultation_external_id"],
Expand All @@ -552,7 +544,21 @@ def has_read_permission(request):
)
)

@staticmethod
def has_write_permission(request):
return (
request.user.user_type not in User.READ_ONLY_TYPES
and DailyRound.has_read_permission(request)
)

@staticmethod
def has_analyse_permission(request):
return DailyRound.has_read_permission(request)

def has_object_read_permission(self, request):
if request.user.user_type < User.TYPE_VALUE_MAP["NurseReadOnly"]:
return False

return (
request.user.is_superuser
or (
Expand Down Expand Up @@ -582,38 +588,9 @@ def has_object_read_permission(self, request):
)

def has_object_write_permission(self, request):
if (
request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"]
or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"]
or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"]
):
return False
return (
request.user.is_superuser
or (
self.consultation.patient.facility
and self.consultation.patient.facility == request.user.home_facility
)
or (
self.consultation.assigned_to == request.user
or request.user == self.consultation.patient.assigned_to
)
or (
request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]
and (
self.consultation.patient.facility
and request.user.district
== self.consultation.patient.facility.district
)
)
or (
request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]
and (
self.consultation.patient.facility
and request.user.state
== self.consultation.patient.facility.district
)
)
request.user.user_type not in User.READ_ONLY_TYPES
and self.has_object_read_permission(request)
)

def has_object_asset_read_permission(self, request):
Expand Down
24 changes: 0 additions & 24 deletions care/facility/models/mixins/permissions/asset.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
from dry_rest_permissions.generics import DRYPermissions

from care.facility.models.mixins.permissions.base import BasePermissionMixin
from care.users.models import User


class IsAssetUser:
def has_permission(self, request, view):
Expand All @@ -23,24 +20,3 @@ class DRYAssetPermissions(DRYPermissions):

def _get_action(self, action):
return f"asset_{super()._get_action(action)}"


class AssetsPermissionMixin(BasePermissionMixin):
def has_object_read_permission(self, request):
return True

def has_object_write_permission(self, request):
if (
request.user.user_type == User.TYPE_VALUE_MAP["DistrictReadOnlyAdmin"]
or request.user.user_type == User.TYPE_VALUE_MAP["StateReadOnlyAdmin"]
or request.user.user_type == User.TYPE_VALUE_MAP["StaffReadOnly"]
):
return False

return True

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

def has_object_destroy_permission(self, request):
return self.has_object_write_permission(request)
Loading

0 comments on commit a92b60a

Please sign in to comment.