class Infrastructure(SequenceMixin, AbstractBase): """ Health specilities. """ name = models.CharField(max_length=255, unique=True) description = models.TextField(null=True, blank=True) abbreviation = models.CharField( max_length=50, null=True, blank=True, help_text='A short form for the infrastructure') category = models.ForeignKey( InfrastructureCategory, on_delete=models.PROTECT, help_text="The classification that the specialities lies in.", related_name='category_infrastructure') code = SequenceField(unique=True, editable=False) def save(self, *args, **kwargs): if not self.code: self.code = self.generate_next_code_sequence() super(Infrastructure, self).save(*args, **kwargs) @property def category_name(self): return self.category.name def __str__(self): return self.name class Meta(AbstractBase.Meta): verbose_name_plural = 'infrastructure'
class CommunityHealthUnit(SequenceMixin, AbstractBase): """ This is a health service delivery structure within a defined geographical area covering a population of approximately 5,000 people. Each unit is assigned 2 Community Health Extension Workers (CHEWs) and community health volunteers who offer promotive, preventative and basic curative health services """ name = models.CharField(max_length=100) code = SequenceField(unique=True) facility = models.ForeignKey( Facility, help_text='The facility on which the health unit is tied to.') status = models.ForeignKey(Status) community = models.ForeignKey( Community, help_text='Community area within which the health unit is located') households_monitored = models.PositiveIntegerField(default=0) date_established = models.DateField(default=timezone.now) contacts = models.ManyToManyField(Contact, through=CommunityHealthUnitContact) def __unicode__(self): return self.name def save(self, *args, **kwargs): if not self.code: self.code = self.generate_next_code_sequence() super(CommunityHealthUnit, self).save(*args, **kwargs)
class Service(SequenceMixin, AbstractBase): """ A health service. """ name = models.CharField(max_length=255, unique=True) description = models.TextField(null=True, blank=True) abbreviation = models.CharField( max_length=50, null=True, blank=True, help_text='A short form for the service e.g FANC for Focused ' 'Antenatal Care') category = models.ForeignKey( ServiceCategory, help_text="The classification that the service lies in.") code = SequenceField(unique=True, editable=False) options = models.ManyToManyField(Option, through='ServiceOption') def save(self, *args, **kwargs): if not self.code: self.code = self.generate_next_code_sequence() super(Service, self).save(*args, **kwargs) def __unicode__(self): return self.name class Meta(AbstractBase.Meta): verbose_name_plural = 'services'
class Community(SequenceMixin, AbstractBase): """ A certain area within a ward. """ name = models.CharField(max_length=100) code = SequenceField(unique=True) ward = models.ForeignKey(Ward) def save(self, *args, **kwargs): if not self.code: self.code = self.generate_next_code_sequence() super(Community, self).save(*args, **kwargs) class Meta(object): verbose_name_plural = 'communities' unique_together = ( 'name', 'ward', )
class AdminOffice(SequenceMixin, AbstractBase): """ The administration offices from the sub-county level to the national level. If the county and sub-county are null then the offices are assumed to be at the national level. """ code = SequenceField( unique=True, help_text="A unique number to identify the admin office.", editable=False) old_code = models.IntegerField(null=True, blank=True) county = models.ForeignKey( County, null=True, blank=True, on_delete=models.PROTECT, ) sub_county = models.ForeignKey( SubCounty, null=True, blank=True, on_delete=models.PROTECT, ) constituency = models.ForeignKey( Constituency, null=True, blank=True, on_delete=models.PROTECT, ) coordinates = gis_models.PointField(null=True, blank=True) name = models.CharField(max_length=100) email = models.EmailField(null=True, blank=True) phone_number = models.CharField(max_length=100, null=True, blank=True) is_national = models.BooleanField(default=False) closed = models.BooleanField(default=False) def save(self, *args, **kwargs): if not self.code: self.code = self.generate_next_code_sequence() super(AdminOffice, self).save(*args, **kwargs)
class Owner(AbstractBase, SequenceMixin): """ Entity that has exclusive legal rights to the facility. For the master facility list, ownership especially for the faith-based facilities is broadened to also include the body that coordinates service delivery and health programmes. Therefore, the Christian Health Association of Kenya (CHAK), Kenya Episcopal Conference (KEC), or Supreme Council of Kenya Muslim (SUPKEM) will be termed as owners though in fact the facilities under them are owned by the individual churches, mosques, or communities affiliated with the faith. """ name = models.CharField( max_length=100, unique=True, help_text="The name of owner e.g Ministry of Health.") description = models.TextField( null=True, blank=True, help_text="A brief summary of the owner.") code = SequenceField( unique=True, help_text="A unique number to identify the owner." "Could be up to 7 characteres long.", editable=False) abbreviation = models.CharField( max_length=30, null=True, blank=True, help_text="Short form of the name of the owner e.g Ministry of health" " could be shortened as MOH") owner_type = models.ForeignKey( OwnerType, help_text="The classification of the owner e.g INDIVIDUAL", on_delete=models.PROTECT) def save(self, *args, **kwargs): if not self.code: self.code = self.generate_next_code_sequence() super(Owner, self).save(*args, **kwargs) def __unicode__(self): return self.name
class CommunityHealthUnit(SequenceMixin, AbstractBase): """ This is a health service delivery structure within a defined geographical area covering a population of approximately 5,000 people. Each unit is assigned 2 Community Health Extension Workers (CHEWs) and community health volunteers who offer promotive, preventative and basic curative health services """ name = models.CharField(max_length=100) code = SequenceField(unique=True) facility = models.ForeignKey( Facility, help_text='The facility on which the health unit is tied to.') status = models.ForeignKey(Status) households_monitored = models.PositiveIntegerField( help_text='The number of house holds a CHU is in-charge of') date_established = models.DateField(default=timezone.now) date_operational = models.DateField(null=True, blank=True) is_approved = models.BooleanField(default=False) approval_comment = models.TextField(null=True, blank=True) approval_date = models.DateTimeField(null=True, blank=True) location = models.CharField(max_length=255, null=True, blank=True) is_closed = models.BooleanField(default=False) closing_comment = models.TextField(null=True, blank=True) is_rejected = models.BooleanField(default=False) rejection_reason = models.TextField(null=True, blank=True) has_edits = models.BooleanField( default=False, help_text='Indicates that a community health unit has updates that are' ' pending approval') def __str__(self): return self.name @property def workers(self): from .serializers import CommunityHealthWorkerPostSerializer return CommunityHealthWorkerPostSerializer(self.health_unit_workers, many=True).data def validate_facility_is_not_closed(self): if self.facility.closed: raise ValidationError({ "facility": [ "A Community Unit cannot be attached to a closed " "facility" ] }) def validate_either_approved_or_rejected_and_not_both(self): error = { "approve/reject": [ "A Community Unit cannot be approved and" " rejected at the same time " ] } values = [self.is_approved, self.is_rejected] if values.count(True) > 1: raise ValidationError(error) def validate_date_operation_is_less_than_date_established(self): if self.date_operational and self.date_established: if self.date_established > self.date_operational: raise ValidationError({ "date_operational": [ "Date operation cannot be greater than date " "established" ] }) def validate_date_established_not_in_future(self): """ Only the date operational needs to be validated. date_established should always be less then the date_operational. Thus is the date_operational is not in future the date_established is also not in future """ today = datetime.datetime.now().date() if self.date_operational and self.date_operational > today: raise ValidationError({ "date_operational": ["The date operational cannot be in the future"] }) @property def contacts(self): return [{ "id": con.id, "contact_id": con.contact.id, "contact": con.contact.contact, "contact_type": con.contact.contact_type.id, "contact_type_name": con.contact.contact_type.name } for con in CommunityHealthUnitContact.objects.filter( health_unit=self)] @property def json_features(self): return { "geometry": { "coordinates": [ self.facility.facility_coordinates_through.coordinates[0], self.facility.facility_coordinates_through.coordinates[1] ] }, "properties": { "ward": self.facility.ward.id, "constituency": self.facility.ward.constituency.id, "county": self.facility.ward.county.id } } def clean(self): super(CommunityHealthUnit, self).clean() self.validate_facility_is_not_closed() self.validate_either_approved_or_rejected_and_not_both() self.validate_date_operation_is_less_than_date_established() self.validate_date_established_not_in_future() @property def pending_updates(self): try: chu = ChuUpdateBuffer.objects.get(is_approved=False, is_rejected=False, health_unit=self) return chu.updates except ChuUpdateBuffer.DoesNotExist: return {} @property def latest_update(self): try: chu = ChuUpdateBuffer.objects.get(is_approved=False, is_rejected=False, health_unit=self) return chu except ChuUpdateBuffer.DoesNotExist: return None def save(self, *args, **kwargs): if not self.code: self.code = self.generate_next_code_sequence() super(CommunityHealthUnit, self).save(*args, **kwargs) @property def average_rating(self): return self.chu_ratings.aggregate(r=models.Avg('rating'))['r'] or 0 @property def rating_count(self): return self.chu_ratings.count() class Meta(AbstractBase.Meta): unique_together = ( 'name', 'facility', ) permissions = ( ("view_rejected_chus", "Can see the rejected community health units"), ("can_approve_chu", "Can approve or reject a Community Health Unit"), )
class Facility(SequenceMixin, AbstractBase): """ A health institution in Kenya. The health institution considered as facilities include: Health Centres, Dispensaries, Hospitals etc. """ name = models.CharField( max_length=100, unique=True, help_text='This is the official name of the facility') code = SequenceField( unique=True, editable=False, help_text='A sequential number allocated to each facility') abbreviation = models.CharField( max_length=30, null=True, blank=True, help_text='A short name for the facility.') description = models.TextField( null=True, blank=True, help_text="A brief summary of the Facility") location_desc = models.TextField( null=True, blank=True, help_text="This field allows a more detailed description of how to" "locate the facility e.g Joy medical clinic is in Jubilee Plaza" "7th Floor") number_of_beds = models.PositiveIntegerField( default=0, help_text="The number of beds that a facilty has. e.g 0") number_of_cots = models.PositiveIntegerField( default=0, help_text="The number of cots that a facility has e.g 0") open_whole_day = models.BooleanField( default=False, help_text="Is the facility open 24 hours a day?") open_whole_week = models.BooleanField( default=False, help_text="Is the facility open the entire week?") is_classified = models.BooleanField( default=False, help_text="Should the facility geo-codes be visible to the public?" "Certain facilities are kept 'off-the-map'") is_published = models.BooleanField( default=False, help_text="Should be True if the facility is to be seen on the " "public MFL site") facility_type = models.ForeignKey( FacilityType, help_text="This depends on who owns the facilty. For MOH facilities," "type is the gazetted classification of the facilty." "For Non-MOH check under the respective owners.", on_delete=models.PROTECT) operation_status = models.ForeignKey( FacilityStatus, null=True, blank=True, help_text="Indicates whether the facility" "has been approved to operate, is operating, is temporarily" "non-operational, or is closed down") ward = models.ForeignKey( Ward, on_delete=models.PROTECT, help_text="County ward in which the facility is located") owner = models.ForeignKey( Owner, help_text="A link to the organization that owns the facility") officer_in_charge = models.ForeignKey( Officer, null=True, blank=True, help_text="The officer in charge of the facility") physical_address = models.ForeignKey( PhysicalAddress, null=True, blank=True, help_text="Postal and courier addressing for the facility") contacts = models.ManyToManyField( Contact, through=FacilityContact, help_text='Facility contacts - email, phone, fax, postal etc') parent = models.ForeignKey( 'self', help_text='Indicates the umbrella facility of a facility', null=True, blank=True) attributes = models.TextField(null=True, blank=True) @property def current_regulatory_status(self): try: return self.regulatory_details.all()[0] except IndexError: return [] def save(self, *args, **kwargs): if not self.code: self.code = self.generate_next_code_sequence() super(Facility, self).save(*args, **kwargs) @property def is_approved(self): approvals = FacilityApproval.objects.filter(facility=self).count() if approvals: return True else: False def __unicode__(self): return self.name class Meta(AbstractBase.Meta): verbose_name_plural = 'facilities'
class CommunityHealthUnit(SequenceMixin, AbstractBase): """ This is a health service delivery structure within a defined geographical area covering a population of approximately 5,000 people. Each unit is assigned 2 Community Health Extension Workers (CHEWs) and community health volunteers who offer promotive, preventative and basic curative health services """ name = models.CharField(max_length=100) code = SequenceField(unique=True, editable=False, help_text='A sequential number allocated to each chu', null=True, blank=True) facility = models.ForeignKey( Facility, help_text='The facility on which the health unit is tied to.') status = models.ForeignKey(Status, on_delete=models.PROTECT) households_monitored = models.PositiveIntegerField( default=0, help_text='The number of house holds a CHU is in-charge of') date_established = models.DateField(default=timezone.now) date_operational = models.DateField(null=True, blank=True) is_approved = models.NullBooleanField( blank=True, null=True, help_text='Determines if a chu has been approved') approval_comment = models.TextField(null=True, blank=True) approval_date = models.DateTimeField(null=True, blank=True) location = models.CharField(max_length=255, null=True, blank=True) is_closed = models.BooleanField(default=False) closing_comment = models.TextField(null=True, blank=True) is_rejected = models.BooleanField(default=False) rejection_reason = models.TextField(null=True, blank=True) has_edits = models.BooleanField( default=False, help_text='Indicates that a community health unit has updates that are' ' pending approval') number_of_chvs = models.PositiveIntegerField( default=0, help_text='Number of Community Health volunteers in the CHU') def __str__(self): return self.name @property def workers(self): from .serializers import CommunityHealthWorkerPostSerializer return CommunityHealthWorkerPostSerializer(self.health_unit_workers, many=True).data def validate_facility_is_not_closed(self): if self.facility.closed: raise ValidationError({ "facility": [ "A Community Unit cannot be attached to a closed " "facility" ] }) def validate_either_approved_or_rejected_and_not_both(self): error = { "approve/reject": [ "A Community Unit cannot be approved and" " rejected at the same time " ] } values = [self.is_approved, self.is_rejected] if values.count(True) > 1: raise ValidationError(error) def validate_date_operation_is_less_than_date_established(self): if self.date_operational and self.date_established: if self.date_established > self.date_operational: raise ValidationError({ "date_operational": [ "Date operation cannot be greater than date " "established" ] }) def validate_date_established_not_in_future(self): """ Only the date operational needs to be validated. date_established should always be less then the date_operational. Thus is the date_operational is not in future the date_established is also not in future """ today = datetime.datetime.now().date() if self.date_operational and self.date_operational > today: raise ValidationError({ "date_operational": ["The date operational cannot be in the future"] }) def validate_comment_required_on_rejection(self): """ Comments will only be required on the rejection of a community health unit. The approvals will not require comments. """ if self.is_rejected and not self.rejection_reason: raise ValidationError({ "rejection_reason": ["Please provide the reason for rejecting the CHU"] }) @property def contacts(self): return [{ "id": con.id, "contact_id": con.contact.id, "contact": con.contact.contact, "contact_type": con.contact.contact_type.id, "contact_type_name": con.contact.contact_type.name } for con in CommunityHealthUnitContact.objects.filter( health_unit=self)] @property def json_features(self): return { "geometry": { "coordinates": [ self.facility.facility_coordinates_through.coordinates[0], self.facility.facility_coordinates_through.coordinates[1] ] }, "properties": { "ward": self.facility.ward.id, "constituency": self.facility.ward.constituency.id, "county": self.facility.ward.county.id } } def clean(self): super(CommunityHealthUnit, self).clean() self.validate_facility_is_not_closed() self.validate_either_approved_or_rejected_and_not_both() self.validate_date_operation_is_less_than_date_established() self.validate_date_established_not_in_future() self.validate_comment_required_on_rejection() @property def pending_updates(self): try: chu = ChuUpdateBuffer.objects.latest(is_approved=False, is_rejected=False, health_unit=self) return chu.updates except ChuUpdateBuffer.DoesNotExist: return {} @property def latest_update(self): try: chu = ChuUpdateBuffer.objects.latest(is_approved=False, is_rejected=False, health_unit=self) return chu except ChuUpdateBuffer.DoesNotExist: return None def save(self, *args, **kwargs): # new chus that have just been added but not approved yet if not self.code and not self.is_approved: super(CommunityHealthUnit, self).save(*args, **kwargs) # existing chus that were approved previously and have been updated if self.code and self.is_approved: if settings.PUSH_TO_DHIS: self.push_chu_to_dhis2() super(CommunityHealthUnit, self).save(*args, **kwargs) # chus that have just been approved but don't have a code yet # and have not been pushed to DHIS yet if self.is_approved and not self.code: self.code = self.generate_next_code_sequence() if settings.PUSH_TO_DHIS: self.push_chu_to_dhis2() super(CommunityHealthUnit, self).save(*args, **kwargs) @property def average_rating(self): return self.chu_ratings.aggregate(r=models.Avg('rating'))['r'] or 0 @property 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() unit_uuid_status = dhisauth.get_org_unit_id(self.code) unit_uuid = unit_uuid_status[0] new_chu_payload = { "id": unit_uuid, "code": str(self.code), "name": str(self.name), "shortName": str(self.name), "displayName": str(self.name), "parent": { "id": facility_dhis_id }, "openingDate": self.date_operational.strftime("%Y-%m-%d"), } metadata_payload = {"keph": 'axUnguN4QDh'} if unit_uuid_status[1] == 'retrieved': r = requests.put( settings.DHIS_ENDPOINT + "api/organisationUnits/" + new_chu_payload.pop('id'), auth=(settings.DHIS_USERNAME, settings.DHIS_PASSWORD), headers={"Accept": "application/json"}, json=new_chu_payload) print("Update CHU Response", r.url, r.status_code, r.json()) LOGGER.info("Update CHU Response: %s" % r.text) else: r = requests.post(settings.DHIS_ENDPOINT + "api/organisationUnits", auth=(settings.DHIS_USERNAME, settings.DHIS_PASSWORD), headers={"Accept": "application/json"}, json=new_chu_payload) print("Create CHU Response", r.url, r.status_code, r.json()) LOGGER.info("Create CHU Response: %s" % 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) def push_chu_metadata(self, metadata_payload, chu_uid): # Keph Level import requests r_keph = requests.post( settings.DHIS_ENDPOINT + "api/organisationUnitGroups/" + metadata_payload['keph'] + "/organisationUnits/" + chu_uid, auth=(settings.DHIS_USERNAME, settings.DHIS_PASSWORD), headers={"Accept": "application/json"}, ) LOGGER.info('Metadata CUs pushed successfullly') def get_facility_dhis2_parent_id(self): 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: 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" ] }) class Meta(AbstractBase.Meta): unique_together = ( 'name', 'facility', ) permissions = ( ("view_rejected_chus", "Can see the rejected community health units"), ("can_approve_chu", "Can approve or reject a Community Health Unit"), )
def test_get_prepared_value(self): seq = SequenceField() self.assertEqual(seq.get_prep_value(value=''), None)