Skip to content

Commit

Permalink
feat: program facilities, facility specific content
Browse files Browse the repository at this point in the history
  • Loading branch information
Muchogoc committed Jan 27, 2023
1 parent 1f53908 commit de733f3
Show file tree
Hide file tree
Showing 19 changed files with 247 additions and 66 deletions.
8 changes: 4 additions & 4 deletions config/api_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,25 @@

app_name = "api"
urlpatterns = router.urls + [
path("users/<pk>", UserAPIView.as_view(), name="users-detail"),
path("users/<pk>/", UserAPIView.as_view(), name="users-detail"),
path(
"users/",
UserAPIView.as_view(),
name="users-general",
),
path("organisations/<pk>", OrganisationAPIView.as_view(), name="organisations-detail"),
path("organisations/<pk>/", OrganisationAPIView.as_view(), name="organisations-detail"),
path(
"organisations/",
OrganisationAPIView.as_view(),
name="organisations-general",
),
path("programs/<pk>", ProgramAPIView.as_view(), name="programs-detail"),
path("programs/<pk>/", ProgramAPIView.as_view(), name="programs-detail"),
path(
"programs/",
ProgramAPIView.as_view(),
name="programs-general",
),
path("clients/<pk>", ClientAPIView.as_view(), name="clients-detail"),
path("clients/<pk>/", ClientAPIView.as_view(), name="clients-detail"),
path(
"clients/",
ClientAPIView.as_view(),
Expand Down
1 change: 0 additions & 1 deletion config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,6 @@
"django_filters.rest_framework.DjangoFilterBackend",
"rest_framework.filters.OrderingFilter",
"mycarehub.common.filters.OrganisationFilterBackend",
"mycarehub.common.filters.AllottedFacilitiesFilterBackend",
),
"DEFAULT_PAGINATION_CLASS": (
"rest_framework_datatables.pagination.DatatablesPageNumberPagination"
Expand Down
3 changes: 1 addition & 2 deletions mycarehub/common/filters/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from .base_filters import CommonFieldsFilterset
from .common_filters import FacilityFilter, UserFacilityAllotmentFilter
from .custom_filter_backends import AllottedFacilitiesFilterBackend, OrganisationFilterBackend
from .custom_filter_backends import OrganisationFilterBackend

__all__ = [
"AllottedFacilitiesFilterBackend",
"CommonFieldsFilterset",
"FacilityFilter",
"OrganisationFilterBackend",
Expand Down
22 changes: 0 additions & 22 deletions mycarehub/common/filters/custom_filter_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,12 @@

from mycarehub.content.models import ContentItemCategory

from ..models import UserFacilityAllotment


class AllottedFacilitiesFilterBackend(filters.BaseFilterBackend):
"""Users are only allowed to view records relating to facilities they have been allotted to.
For this to work, the attribute `facility_field_lookup` must be present on a view. The
attribute should contain a lookup to a facility and should be usable from the queryset
returned by the view. If the aforementioned attribute is not present on a view, then no
filtering is performed and the queryset is returned as is.
"""

def filter_queryset(self, request, queryset, view):
"""Exclude records associated to facilities that the requesting user is not allotted."""
lookup = getattr(view, "facility_field_lookup", None)
if not lookup:
return queryset
allotted_facilities = UserFacilityAllotment.get_facilities_for_user(request.user)
qs_filter = {"%s__in" % lookup: allotted_facilities}
return queryset.filter(**qs_filter)


class OrganisationFilterBackend(filters.BaseFilterBackend):
"""Users are only allowed to view records in their organisation."""

def filter_queryset(self, request, queryset, view):
"""Filter all records that have an organisation field by user org."""
# Exclude models without an organisation
if queryset.model in [ContentItemCategory]:
return queryset
return queryset.filter(organisation=request.user.organisation)
27 changes: 27 additions & 0 deletions mycarehub/common/migrations/0003_auto_20230124_1200.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 3.2.16 on 2023-01-24 09:00

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('common', '0002_initial'),
]

operations = [
migrations.RemoveField(
model_name='facility',
name='fhir_organization_id',
),
migrations.AlterField(
model_name='facility',
name='county',
field=models.CharField(blank=True, choices=[('Nairobi', 'Nairobi'), ('Kajiado', 'Kajiado')], max_length=64, null=True),
),
migrations.AlterField(
model_name='facility',
name='mfl_code',
field=models.IntegerField(blank=True, help_text='MFL Code', null=True, unique=True),
),
]
18 changes: 18 additions & 0 deletions mycarehub/common/migrations/0004_program_facilities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.16 on 2023-01-25 12:02

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('common', '0003_auto_20230124_1200'),
]

operations = [
migrations.AddField(
model_name='program',
name='facilities',
field=models.ManyToManyField(blank=True, null=True, related_name='programs', to='common.Facility'),
),
]
12 changes: 4 additions & 8 deletions mycarehub/common/models/common_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from django.urls import reverse
from django.utils import timezone

from ..constants import WHITELIST_COUNTIES
from ..utils import get_constituencies, get_counties, get_sub_counties, get_wards
from .base_models import AbstractBase, AbstractBaseManager, AbstractBaseQuerySet

Expand All @@ -24,9 +23,7 @@ class FacilityQuerySet(AbstractBaseQuerySet):

def mycarehub_facilities(self):
"""Return all the facilities that are part of the FYJ program."""
return self.active().filter(
county__in=WHITELIST_COUNTIES,
)
return self.active()


# =============================================================================
Expand Down Expand Up @@ -58,10 +55,9 @@ class Facility(AbstractBase):

name = models.TextField(unique=True)
description = models.TextField(blank=True, default="")
mfl_code = models.IntegerField(unique=True, help_text="MFL Code")
county = models.CharField(max_length=64, choices=get_counties())
mfl_code = models.IntegerField(unique=True, help_text="MFL Code", blank=True, null=True)
county = models.CharField(max_length=64, choices=get_counties(), null=True, blank=True)
phone = models.CharField(max_length=15, null=True, blank=True)
fhir_organization_id = models.CharField(unique=True, max_length=64, blank=True, null=True)

objects = FacilityManager()

Expand All @@ -78,7 +74,7 @@ def check_facility_name_longer_than_three_characters(self):
raise ValidationError("the facility name should exceed 3 characters")

def __str__(self):
return f"{self.name} - {self.mfl_code} ({self.county})"
return f"{self.name}"

class Meta(AbstractBase.Meta):
verbose_name_plural = "facilities"
Expand Down
2 changes: 2 additions & 0 deletions mycarehub/common/models/program_models.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from django.db import models

from .base_models import AbstractBase
from .common_models import Facility


class Program(AbstractBase):

name = models.TextField(max_length=100, unique=True)
facilities = models.ManyToManyField(Facility, blank=True, null=True, related_name="programs")

class Meta:
unique_together = (
Expand Down
15 changes: 14 additions & 1 deletion mycarehub/common/serializers/common_serializers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Common serializers."""
import logging
import uuid

from django import forms
from django.contrib.auth import get_user_model
Expand Down Expand Up @@ -28,6 +29,8 @@ class Meta:


class FacilitySerializer(BaseSerializer):
id = serializers.UUIDField(read_only=False, default=uuid.uuid4)

class Meta(BaseSerializer.Meta):
model = Facility
fields = "__all__"
Expand All @@ -53,7 +56,17 @@ class Meta(BaseSerializer.Meta):
class ProgramSerializer(BaseSerializer):
class Meta(BaseSerializer.Meta):
model = Program
fields = ["id", "name"]
fields = ["id", "name", "facilities"]

def update(self, instance, validated_data):
facilities = validated_data.pop("facilities", None)
if facilities:
instance.facilities.clear()

for facility in facilities:
instance.facilities.add(facility)

return super().update(instance, validated_data)


class OrganisationRegistrationSerializer(FormSerializer):
Expand Down
4 changes: 0 additions & 4 deletions mycarehub/common/serializers/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import logging

from rest_framework import exceptions, serializers
from rest_framework.serializers import ValidationError

from mycarehub.common.models import Organisation

Expand Down Expand Up @@ -87,9 +86,6 @@ def populate_audit_fields(self, data, is_create):

def create(self, validated_data):
"""Ensure that ids are not supplied when creating new instances."""
initial_data_id = isinstance(self.initial_data, dict) and self.initial_data.get("id")
if initial_data_id or validated_data.get("id"):
raise ValidationError({"id": "You are not allowed to pass object with an id"})
self.populate_audit_fields(validated_data, True)
return super().create(validated_data)

Expand Down
99 changes: 81 additions & 18 deletions mycarehub/common/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ def setUp(self):
organisation=self.global_organisation,
)
self.data = {
"id": uuid.uuid4(),
"name": fake.name(),
"mfl_code": random.randint(1, 999_999_999),
"county": "Nairobi",
Expand Down Expand Up @@ -201,24 +202,6 @@ def test_create_facility_no_organisation(self):
assert response.status_code == 201, response.json()
assert response.data["mfl_code"] == data["mfl_code"]

def test_create_facility_error_supplied_id(self):
"""Test add facility."""
data = {
"id": uuid.uuid4(),
"name": fake.name(),
"mfl_code": random.randint(1, 999_999_999),
"county": random.choice(WHITELIST_COUNTIES),
"is_mycarehub_facility": True,
"operation_status": "Operational",
"organisation": self.global_organisation.pk,
}

response = self.client.post(self.url_list, data)
assert response.status_code == 400, response.json()
assert (
"You are not allowed to pass object with an id" in response.json()["id"]
), response.json()

def test_create_facility_error_bad_organisation(self):
"""Test add facility."""
data = {
Expand Down Expand Up @@ -664,6 +647,22 @@ def test_program_registration(user_with_all_permissions, client):
assert response_data["id"] is not None


def test_program_list(user_with_all_permissions, client):
client.force_login(user_with_all_permissions)
url = reverse("api:programs-general")

response = client.get(
url,
content_type="application/json",
accept="application/json",
)

assert response.status_code == status.HTTP_200_OK

response_data = response.json()
assert response_data[0]["id"] is not None


def test_program_registration_invalid_input(user_with_all_permissions, client):
client.force_login(user_with_all_permissions)
url = reverse("api:programs-general")
Expand All @@ -678,3 +677,67 @@ def test_program_registration_invalid_input(user_with_all_permissions, client):
)

assert response.status_code == status.HTTP_400_BAD_REQUEST


def test_program_patch_no_facilities(user_with_all_permissions, client):
program = baker.make(Program)

client.force_login(user_with_all_permissions)
url = reverse("api:programs-detail", kwargs={"pk": program.id})

response = client.patch(
url,
data={
"name": fake.name(),
},
content_type="application/json",
accept="application/json",
)

assert response.status_code == status.HTTP_200_OK

response_data = response.json()
assert response_data["id"] == str(program.id)


def test_program_patch_facilities(user_with_all_permissions, client):
program = baker.make(Program)

client.force_login(user_with_all_permissions)
url = reverse("api:programs-detail", kwargs={"pk": program.id})

facility = baker.make(Facility)

response = client.patch(
url,
data={
"name": fake.name(),
"facilities": [facility.id],
},
content_type="application/json",
accept="application/json",
)

assert response.status_code == status.HTTP_200_OK

response_data = response.json()
assert response_data["id"] == str(program.id)


def test_program_patch_invalid_data(user_with_all_permissions, client):
program = baker.make(Program)

client.force_login(user_with_all_permissions)
url = reverse("api:programs-detail", kwargs={"pk": program.id})

response = client.patch(
url,
data={
"name": 12123123,
"facilities": ["invalid"],
},
content_type="application/json",
accept="application/json",
)

assert response.status_code == status.HTTP_400_BAD_REQUEST
2 changes: 1 addition & 1 deletion mycarehub/common/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def test_facility_string_representation():
updated_by=updated_by,
)
facility.save()
assert str(facility) == f"{facility_name} - {mfl_code} (Nairobi)"
assert str(facility) == f"{facility_name}"


def test_google_application_credentials():
Expand Down
Loading

0 comments on commit de733f3

Please sign in to comment.