Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
f625342
Updated facilities/utils.py
ertush Jul 12, 2024
a47f942
Facility push to KHIS Fix
ertush Jul 12, 2024
26dd827
Added github action to deploy to production
ertush Jul 12, 2024
61f8e48
Renamed the github workflow for deploying to production
ertush Jul 12, 2024
a4c99fd
Fixed Issue of Facility Update in facility_models.py
ertush Jul 12, 2024
2b2899c
Hotfix update for approve national level update
ertush Jul 15, 2024
6ac3003
Updated line 238 of mfr-api-prod: chul/models.py
ertush Jul 17, 2024
fc827aa
Reversed: Updated line 238 of mfr-api-prod: chul/models.py
ertush Jul 17, 2024
c4ab289
Changed the first argument of push_facility_updates_to_dhis2 method …
ertush Jul 24, 2024
485068f
Attempt #2 to fix the issue of pushing new facility to KHIS on facili…
ertush Jul 24, 2024
102faea
Updated line 122 of chul/models.py
ertush Jul 30, 2024
179876a
Updated lines 124-126 of chul/models.py
ertush Jul 30, 2024
8ede191
Updated lines 124-126 of chul/models.py
ertush Jul 30, 2024
77339cc
Updated lines 124-126 of chul/models.py
ertush Jul 30, 2024
07b3ec4
Updated lines 124-126 of chul/models.py
ertush Jul 30, 2024
cc87e4e
Updated lines 1404-1405
ertush Aug 2, 2024
a83eacc
Updated lines 1404-1405
ertush Aug 2, 2024
02577f1
Updated lines 1404-1405
ertush Aug 2, 2024
4b9c50d
Added debug statement
ertush Aug 2, 2024
2473d66
Updated lines 1404-1405
ertush Aug 2, 2024
11a915d
added a debug line in facility_models.py: 140
ertush Aug 2, 2024
1b24960
added a debug line in facility_models.py: 140
ertush Aug 2, 2024
6fa5d01
added a debug line in facility_models.py: 144
ertush Aug 2, 2024
9bd2985
added a debug line in facility_models.py: 146
ertush Aug 2, 2024
d81c497
added a debug line in facility_models.py: 146
ertush Aug 2, 2024
45984c3
added a debug line in facility_models.py: 147
ertush Aug 2, 2024
a7f585d
Updated line 1405 of facility_models.py
ertush Aug 2, 2024
689ebe9
DEBUG hotfix update
ertush Aug 2, 2024
051bff0
Updates lines 1406-1407
ertush Aug 2, 2024
0787d18
Updates lines 1406-1407
ertush Aug 2, 2024
0268fa3
Refactored line 2331
ertush Aug 2, 2024
8bedb01
Refactored line 277
ertush Aug 2, 2024
8e3ed6b
Refactored line 215-216
ertush Aug 2, 2024
3433e98
Refactored line 215-222
ertush Aug 2, 2024
661db9e
Fixed the facility validation issue
ertush Aug 2, 2024
d934662
Added linked facility code check and a validation Error if facility c…
ertush Aug 6, 2024
59fea08
Added linked facility code check and a validation Error if facility c…
ertush Aug 6, 2024
e60f285
Updated push_chu_to_dhis2 in mf-api-prod
ertush Aug 6, 2024
9b88948
Updated push_chu_to_dhis2 in mf-api-prod
ertush Aug 6, 2024
e463757
Fixed issue of CHU not submitting
ertush Aug 6, 2024
a8b8d17
Updated filter_unpublished_facilities_national_level and facilities_p…
ertush Aug 7, 2024
0135a3b
Updated line 465 of facilities_filters.py
ertush Aug 7, 2024
9fd5483
Updated line 418 of facility_filters
ertush Aug 7, 2024
3683a6f
Added filter_facilities_with_pending_updates method to FacilityFilter…
ertush Aug 8, 2024
e773315
Fixed filter for incomplete facilities
ertush Aug 9, 2024
aa09d6c
Improved function filter_incomplete_facilities
ertush Aug 9, 2024
998a06b
Updated chul/serializer.py; buffer_updates method of CommunityHealthU…
ertush Aug 9, 2024
673c18d
Updated facility_filters line 493
ertush Aug 9, 2024
b0edcf8
Updated line 1051; all commented lines
ertush Aug 9, 2024
3fd5722
Debug: facility_views.py line 660
ertush Sep 4, 2024
01ed35c
Hotfix: users/serializeres.py line 325
ertush Sep 9, 2024
4e26c09
Hotfix: users/serializeres.py line 325
ertush Sep 9, 2024
c20e4a7
Hotfix: users/serializeres.py line 325 #3
ertush Sep 9, 2024
f5da0a9
Hotfix: users/serializeres.py line 325 #4
ertush Sep 9, 2024
685eba6
Added Validation Error in _validate_within_boundaries method lines 41-47
ertush Oct 22, 2024
c1efafe
Added fix to geolocation validation (Removed constituency in geolocat…
ertush Oct 25, 2024
1994ed0
Updated pending_approval filter for Community Units to include CHUs t…
ertush Oct 28, 2024
1bf4ba9
Updated facility_dashboard.py line 118 in get_facility_constituency_s…
ertush Nov 21, 2024
0317c5f
Updated line 179-183 in facility_models.py
ertush Dec 3, 2024
9a33cff
Updated ci-prod.yml and facility_models.py lines 180-184
ertush Dec 3, 2024
da070d3
Updated ci-prod.yml lines 29 - 31
ertush Dec 3, 2024
eb591d1
Updated ci-prod.yml lines 29 - 31
ertush Dec 3, 2024
2b9e72e
updated config/settings/base.py line 170; DEFAULT_PERMISSION_CLASSES
ertush Jan 9, 2025
e83eb5e
Updated the search parameter for facilities from name to official_nam…
ertush Jan 9, 2025
3a6edc0
Updated mfl_gis/views.py line 198; to remove permissions Django permi…
ertush Jan 10, 2025
25a8f76
Updated mfl_gis/views.py line 198; removed permissions for creating a…
ertush Jan 10, 2025
7f0a8ae
Added a comment in settings/base.py line 165
ertush Jan 10, 2025
6fa3e17
Updated facility_filters.py line 97 ++
ertush May 6, 2025
6890cca
Added is_published to FacilityExportExcelMaterialViewFilter
ertush May 6, 2025
82f22dd
updated facility_views.py line: 470
ertush May 26, 2025
d16fca6
updated facility_models.py line: 470
ertush May 26, 2025
71bf29d
serializers/facility_serializers.py
ertush May 27, 2025
d06bc2f
Restoring facility download temporarily
ertush Jun 9, 2025
d0b85dc
Restoring facility download temporarily
ertush Jun 9, 2025
6ea35ad
Reverted back to no rights to download facilities for all users
ertush Jun 9, 2025
01ebaef
Enabled facility export
ertush Jun 13, 2025
6d769e2
Added in_complete_details and is_complete to FacilityExportMaterial
ertush Jun 30, 2025
e1fa992
Updated chul/models.py; lines 590-592
ertush Jul 2, 2025
eedfaee
Removed is_complete in Facility Export Material View
ertush Jul 14, 2025
f0c12ca
Updated chul/serializers.py and added facilty_code in CommunityHealth…
ertush Aug 7, 2025
0c2cd00
Added facility_code filter to CommunityHealthUnitFilter
ertush Aug 7, 2025
356ea3c
Added facility_code filter to CommunityHealthUnitFilter
ertush Aug 7, 2025
dafd16a
Updated facility_code filter to ListCharFilter
ertush Aug 7, 2025
1f36cbe
Removed facility_code filter in CommunityHealthUnitFilter
ertush Aug 7, 2025
1ff8a25
Added a check for facility officer in charge to prevent type error in…
ertush Aug 11, 2025
297ecff
Added LoginRequiredMixin
eric-mutua-k Nov 11, 2025
42e4871
Added LoginRequiredMixin to FacilityOfficerListView
eric-mutua-k Nov 12, 2025
4ac6e96
Added LoginRequiredMixin to FacilityOfficerDetailView
eric-mutua-k Nov 12, 2025
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
37 changes: 37 additions & 0 deletions .github/workflows/ci-prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: CI-Prod

on:
push:
branches: [ mfr-api-prod ]


jobs:

deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Deploy to VPS
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
port: ${{ secrets.SERVER_PORT }}
username: ${{ secrets.SERVER_USER }}
password: ${{ secrets.SERVER_KEY }}
script: |
set -e
cd /opt/mfl_api
if ! [[ -d './.git' ]]; then git init; fi
if ! [[ `git remote -v | awk '{print $1}' | head -n 1` =~ 'origin' ]]; then git remote add origin ${{ github.server_url }}${{ github.username }}/${{ github.repository }}.git; fi
git stash
git pull origin mfr-api-prod









7 changes: 5 additions & 2 deletions chul/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,16 @@ def chu_pending_approval(self, qs, name, value):
return qs.filter(
Q(is_approved=None, is_rejected=False, has_edits=False) |
Q(is_approved=None, is_rejected=False, has_edits=True) |
Q(is_approved=None, is_rejected=True, has_edits=True)
Q(is_approved=None, is_rejected=True, has_edits=True) |
Q(is_approved=False, is_rejected=True, has_edits=True) | # Added
Q(is_approved=False, is_rejected=True, has_edits=False) # Added
)
else:
return qs.filter(
Q(is_approved=None, is_rejected=False, has_edits=False) |
Q(is_approved=None, is_rejected=True, has_edits=False)
)

# def chu_approved(self, qs, name, value):
# if value in TRUTH_NESS:
# return qs.filter(
Expand All @@ -84,6 +86,7 @@ def chu_pending_approval(self, qs, name, value):
name='facility__ward__constituency__county')
sub_county = ListCharFilter(
name='facility__ward__sub_county')
# facility_code = ListCharFilter(name="facility_code")

is_approved = django_filters.TypedChoiceFilter(
choices=BOOLEAN_CHOICES, coerce=strtobool
Expand Down
126 changes: 80 additions & 46 deletions chul/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ def latest_update(self):
)

return chu

except ChuUpdateBuffer.DoesNotExist:
return None

Expand Down Expand Up @@ -304,12 +304,15 @@ def average_rating(self):
def rating_count(self):
return self.chu_ratings.count()


def push_chu_to_dhis2(self):
from facilities.models.facility_models import DhisAuth
import requests

dhisauth = DhisAuth()
dhisauth.get_oauth2_token()
facility_dhis_id = self.get_facility_dhis2_parent_id() if self.facility.reporting_in_dhis else None

facility_dhis_id = self.get_facility_dhis2_parent_id() # if self.facility.reporting_in_dhis else None
unit_uuid_status = dhisauth.get_org_unit_id(self.code)
unit_uuid = unit_uuid_status[0]
new_chu_payload = {
Expand All @@ -330,7 +333,6 @@ def push_chu_to_dhis2(self):

if facility_dhis_id is not None:
if unit_uuid_status[1] == 'retrieved':
LOGGER.info("[>>>>>] Retrieved CHU")

r = requests.put(
settings.DHIS_ENDPOINT + "api/organisationUnits/" + new_chu_payload.pop('id'),
Expand All @@ -340,10 +342,12 @@ def push_chu_to_dhis2(self):
},
json=new_chu_payload
)
print("Update CHU Response", r.url, r.status_code, r.json())
LOGGER.info("Update CHU Response: %s" % r.text)

LOGGER.info("[DEBUG] Response(retrived): {}".format(r.text))


else:
LOGGER.info("[>>>>>] Payload for new CHU: {}".format(new_chu_payload));

r = requests.post(
settings.DHIS_ENDPOINT + "api/organisationUnits",
auth=(settings.DHIS_USERNAME, settings.DHIS_PASSWORD),
Expand All @@ -353,20 +357,24 @@ def push_chu_to_dhis2(self):
json=new_chu_payload
)

print("Create CHU Response", r.url, r.status_code, r.json())
LOGGER.info("Create CHU Response: %s" % r.text)
LOGGER.info("[DEBUG] Response(generated): {}".format(r.text))

if r.json()["status"] != "OK":
LOGGER.error("Failed PUSH: error -> {}".format(r.text))
raise ValidationError(
{
"Error!": ["An error occured while pushing Community Unit to DHIS2. This is may be caused by the "
"existance of an organisation unit with as similar name as to the one you are creating. "
"Or some specific information like codes are not unique"]
}
)
self.push_chu_metadata(metadata_payload, unit_uuid)


if r.json()["status"] != "OK":
LOGGER.error("[DEBUG]: Repsonse(error):{}".format(r.text))

raise ValidationError(
{
"Error!": ["An error occured while pushing Community Unit to DHIS2. This is may be caused by the "
"existance of an organisation unit with as similar name as to the one you are creating. "
"Or some specific information like codes are not unique"]
}
)
self.push_chu_metadata(metadata_payload, unit_uuid)
else:
LOGGER.error("[DEBUG] facility_dhis_id: {}".format(facility_dhis_id))

raise ValidationError(
{
"Error!": ["Could not find Facility DHIS ID, when pushing CU to DHIS"]
Expand All @@ -386,33 +394,43 @@ def push_chu_metadata(self, metadata_payload, chu_uid):
)
LOGGER.info('Metadata CUs pushed successfullly')




def get_facility_dhis2_parent_id(self):
from facilities.models.facility_models import DhisAuth
# from facilities.models.facility_models import DhisAuth
import requests
r = requests.get(
settings.DHIS_ENDPOINT + "api/organisationUnits.json",
auth=(settings.DHIS_USERNAME, settings.DHIS_PASSWORD),
headers={
"Accept": "application/json"
},
params={
"query": self.facility.code,
"fields": "id,name",
"filter": "level:in:[5]",
"paging": "false"
}
)

if len(r.json()["organisationUnits"]) is 1 and "id" in r.json()["organisationUnits"][0]:
if r.json()["organisationUnits"][0]["id"]:
return r.json()["organisationUnits"][0]["id"]
else:
raise ValidationError(
{
"Error!": ["Unable to resolve exact Facility linked to the CHU in DHIS2"]
if hasattr(self, "facility") and hasattr(self.facility, "code"):
r = requests.get(
settings.DHIS_ENDPOINT + "api/organisationUnits.json",
auth=(settings.DHIS_USERNAME, settings.DHIS_PASSWORD),
headers={
"Accept": "application/json"
},
params={
"query": self.facility.code,
"fields": "id,name",
"filter": "level:in:[5]",
"paging": "false"
}
)

if len(r.json()["organisationUnits"]) is 1 and "id" in r.json()["organisationUnits"][0]:
if r.json()["organisationUnits"][0]["id"]:
return r.json()["organisationUnits"][0]["id"]
else:
raise ValidationError(
{
"Error!": ["Unable to find facility with code {} in KHIS.".format(self.facility.code)]
}
)
else:
raise ValidationError({
"Error": ["The linked facility for this CU does not have an MFL code. Therefore it is not in KHIS"]
})


class Meta(AbstractBase.Meta):

unique_together = ('name', 'facility',)
Expand Down Expand Up @@ -544,34 +562,47 @@ def update_basic_details(self):
basic_details['facility_id'] = basic_details.get(
'facility').get('facility_id')
basic_details.pop('facility')


for key, value in basic_details.iteritems():
setattr(self.health_unit, key, value)
if key is not "basic":
setattr(self.health_unit, key, value)
if 'basic' in basic_details:
setattr(self.health_unit, 'facility_id', basic_details.get('basic').get('facility'))
if 'basic' in basic_details.get('basic'):
if 'facility' in basic_details.get('basic').get('basic'):
setattr(self.health_unit, 'facility_id', basic_details.get('basic').get('basic').get('facility'))
else:
if 'facility' in basic_details.get('basic'):
setattr(self.health_unit, 'facility_id', basic_details.get('basic').get('facility'))
self.health_unit.save()

def update_workers(self):

chews = json.loads(self.workers)

for chew in chews:
chew['health_unit'] = self.health_unit
chew['created_by_id'] = self.created_by_id
chew['updated_by_id'] = self.updated_by_id
chew.pop('created_by', None)
chew.pop('updated_by', None)
if 'id' in chew:

if hasattr(chew, 'name'):
chew.pop('name', None)

if hasattr(chew, 'id'):
chew_obj = CommunityHealthWorker.objects.get(
id=chew['id'])
chew_obj.first_name = chew['first_name']
chew_obj.last_name = chew['last_name']
if 'is_incharge' in chew:
if hasattr(chew, 'is_incharge'):
chew_obj.is_incharge = chew['is_incharge']
chew_obj.save()
else:
CommunityHealthWorker.objects.create(**chew)

def update_services(self):

services = json.loads(self.services)
CHUServiceLink.objects.filter(health_unit=self.health_unit).delete()
for service in services:
Expand Down Expand Up @@ -620,9 +651,11 @@ def update_contacts(self):
@property
def updates(self):
updates = {}

if self.basic and self.basic is not None:
json_basic = json.loads(self.basic)
updates['basic'] = json_basic['basic'] if 'basic' in json_basic else json_basic
updates['basic'] = json_basic['basic'] if hasattr(json_basic, 'basic') else json_basic

if self.contacts:
updates['contacts'] = json.loads(self.contacts)
if self.workers:
Expand All @@ -632,6 +665,7 @@ def updates(self):
updates['updated_by'] = self.updated_by.get_full_name
return updates


def clean(self, *args, **kwargs):
if not self.is_approved and not self.is_rejected:
self.health_unit.has_edits = True
Expand Down
74 changes: 72 additions & 2 deletions chul/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class CommunityHealthUnitSerializer(
status_name = serializers.ReadOnlyField(source="status.name")
health_unit_workers = serializers.ReadOnlyField(source='workers')
facility_name = serializers.ReadOnlyField(source='facility.name')
facility_code = serializers.ReadOnlyField(source='facility.code')
facility_ward = serializers.ReadOnlyField(source='facility.ward.name')
facility_constituency = serializers.ReadOnlyField(
source='facility.ward.constituency.name')
Expand Down Expand Up @@ -121,7 +122,17 @@ def buffer_updates(
try:
update = ChuUpdateBuffer.objects.get(
health_unit=chu_instance,
is_approved=False, is_rejected=False)
is_approved=False, is_rejected=False) if len(ChuUpdateBuffer.objects.filter(
health_unit=chu_instance,
is_approved=False, is_rejected=False)) == 1 else ChuUpdateBuffer.objects.filter(
health_unit=chu_instance,
is_approved=False, is_rejected=False)[0] if len(ChuUpdateBuffer.objects.filter(
health_unit=chu_instance,
is_approved=False, is_rejected=False)) > 1 else None

if update is None:
raise ChuUpdateBuffer.DoesNotExist

except ChuUpdateBuffer.DoesNotExist:
update = ChuUpdateBuffer.objects.create(
health_unit=chu_instance,
Expand Down Expand Up @@ -166,6 +177,65 @@ def buffer_updates(
update.contacts = contacts
update.save()

# Previous lateest commit: mfr-api-prod
# def buffer_updates(
# self, validated_data, chu_instance, chews=None, contacts=None,
# services=None):

# try:
# chu_updates = ChuUpdateBuffer.objects.filter(
# health_unit=chu_instance,
# is_approved=False, is_rejected=False)

# update = chu_updates[0] if chu_updates.__len__() > 0 else None

# if update is None:
# raise ChuUpdateBuffer.DoesNotExist

# except ChuUpdateBuffer.DoesNotExist:
# update = ChuUpdateBuffer.objects.create(
# health_unit=chu_instance,
# created_by_id=self.context['request'].user.id,
# updated_by_id=self.context['request'].user.id,
# is_new=True)
# basic_updates = self.get_basic_updates(chu_instance, validated_data)

# update.basic = basic_updates if basic_updates \
# and not update.basic else update.basic

# if chews:
# for chew in chews:
# chew.pop('created', None)
# chew.pop('updated', None)
# chew.pop('updated_by', None)
# chew.pop('created_by', None)

# chews = json.dumps(chews)
# update.workers = chews

# if services:
# for service in services:
# sev_rec = CHUService.objects.get(id=service['service'])
# service.pop('created', None)
# service.pop('updated', None)
# service.pop('updated_by', None)
# service.pop('created_by', None)
# service['name'] = sev_rec.name

# services = json.dumps(services)
# update.services = services

# if contacts:
# for contact in contacts:
# contact_type = ContactType.objects.get(
# id=contact['contact_type'])
# contact['contact_type_name'] = contact_type.name

# contacts = json.dumps(contacts)

# update.contacts = contacts
# update.save()

def _ensure_all_chew_required_provided(self, chew):
if 'first_name' and 'last_name' not in chew:
self.inlined_errors.update({
Expand All @@ -188,7 +258,7 @@ def save_chew(self, instance, chews, context):
chew_obj.last_name = chew['last_name']
chew_obj.is_incharge = chew['is_incharge']
chew_obj.mobile_no = chew['mobile_no']
chew_obj.email = chew['email']
chew_obj.email = chew['email']
chew_obj.save()
else:
chew['health_unit'] = instance.id
Expand Down
Loading