def setUp(self): super(TestApp, self).setUp() type, _ = LocationType.objects.get_or_create(singular="clinic", plural="clinics", slug="clinics") clinic = Location.objects.create(type=type, name="demo", slug="demo") self.clinic_zone= Location.objects.create(type=get_zone_type(), name="child", slug="child", parent=clinic) clinic_worker = self.create_contact(name="clinic_worker", location=clinic, types=[get_clinic_worker_type()]) clinic_worker2 = self.create_contact(name="clinic_worker2", location=self.clinic_zone, types=[get_clinic_worker_type()]) # script = "help_admin > hello world" # self.runScript(script) # connection = Connection.objects.get(identity="help_admin") # help_admin = Contact.objects.create(alias='help_admin', is_active = True, name="help_admin", # location=clinic_zone,is_help_admin = True) # help_admin.types.add(const.get_clinic_worker_type()) # # connection.contact = help_admin # connection.save() cba = self.create_contact(name="cba", location=clinic, types=[get_cba_type()]) cba2 = self.create_contact(name="cba2", location=self.clinic_zone, types=[get_cba_type()]) active_contacts = Contact.active.all() self.all = [clinic_worker, clinic_worker2, cba, cba2] self.expected_keyword_to_groups = \ {"ALL": [clinic_worker, clinic_worker2, cba, cba2], "CLINIC": [clinic_worker, clinic_worker2], "CBA": [cba, cba2], }
def setUp(self): super(TestApp, self).setUp() type, _ = LocationType.objects.get_or_create(singular="clinic", plural="clinics", slug="clinics") district_type, _ = LocationType.objects.get_or_create( singular="district", plural="districts", slug="districts") self.district = Location.objects.create(type=district_type, name="Mansa", slug="403000") self.district2 = Location.objects.create(type=district_type, name="Lusaka", slug="402000") clinic = Location.objects.create(type=type, name="Central Clinic", slug="403020") clinic.parent = self.district clinic.save() self.clinic2 = Location.objects.create(type=type, name="Other Clinic", slug="402020") self.clinic2.parent = self.district2 self.clinic2.save() self.clinic_zone = Location.objects.create(type=get_zone_type(), name="child", slug="child", parent=clinic) clinic_worker = self.create_contact(name="clinic_worker", location=clinic, types=[get_clinic_worker_type()]) clinic_worker2 = self.create_contact(name="clinic_worker2", location=clinic, types=[get_clinic_worker_type()]) cba = self.create_contact(name="cba", location=self.clinic_zone, types=[get_cba_type()]) cba2 = self.create_contact(name="cba2", location=self.clinic_zone, types=[get_cba_type()]) active_contacts = Contact.active.all() self.all = [clinic_worker, clinic_worker2, cba, cba2] self.expected_keyword_to_groups = \ {"ALL": [clinic_worker, clinic_worker2, cba, cba2], "CLINIC": [clinic_worker, clinic_worker2], "CBA": [cba, cba2], }
def _get_or_create_zone(self, clinic, name): # create the zone if it doesn't already exist zone_type = const.get_zone_type() try: # get_or_create does not work with iexact zone = Location.objects.get(name__iexact=name, parent=clinic, type=zone_type) except Location.DoesNotExist: zone = Location.objects.create(name=name, parent=clinic, slug=get_unique_value(Location.objects, "slug", name), type=zone_type) return zone
def _get_or_create_zone(self, clinic, name): # create the zone if it doesn't already exist zone_type = const.get_zone_type() try: # get_or_create does not work with iexact zone = Location.objects.get(name__iexact=name, parent=clinic, type=zone_type) except Location.DoesNotExist: zone = Location.objects.create(name=name, parent=clinic, slug=get_unique_value( Location.objects, "slug", name), type=zone_type) return zone
def get_clinic_or_default(contact): """Gets a clinic associated with the contact""" if contact is None: return None # implementation-wise this is a mess because of the list # of possible clinic types. For now we just return the # first parent that is not a zone type or the location # associated with the contact directly, if no non-zone # parents are found location = contact.location while location: if location.type != get_zone_type(): return location location = location.parent return contact.location
def send_appointment_reminder(patient, default_conn=None, pronouns=None): if pronouns is None: pronouns = {} logger.info('Sending appointment reminder for %s' % patient) if patient.location: logger.debug('using patient location (%s) to find CBAs' % patient.location) # if the cba was registered, we'll have a location on # the patient and can use that information to find the CBAs to whom # we should send the appointment reminders # TODO: also check child locations? connections = list(Connection.objects.filter( contact__types__slug=const.CBA_SLUG, contact__location=patient.location, contact__is_active=True)) logger.debug('found %d CBAs to deliver reminders to' % len(connections)) elif default_conn: logger.debug('no patient location; using default_conn') # if the CBA was not registered, just send the notification to the # CBA who logged the event connections = [default_conn] else: logger.debug('no patient location or default_conn; not sending any ' 'reminders') for connection in connections: if connection.contact: cba_name = ' %s' % connection.contact.name else: cba_name = '' if patient.location: if patient.location.type == const.get_zone_type() and\ patient.location.parent: clinic_name = patient.location.parent.name else: clinic_name = patient.location.name else: clinic_name = 'the clinic' msg = OutgoingMessage(connection, _("Hello%(cba)s. %(patient)s is due " "for their next clinic appointment. Please " "deliver a reminder to this person and ensure " "they visit %(clinic)s within 3 days."), cba=cba_name, patient=patient.name, clinic=clinic_name) msg.send() return connections
def setUp(self): super(TestApp, self).setUp() type, _ = LocationType.objects.get_or_create(singular="clinic", plural="clinics", slug="clinics") district_type, _ = LocationType.objects.get_or_create(singular="district", plural="districts", slug="districts") self.district = Location.objects.create(type=district_type, name="Mansa", slug="403000") self.district2 = Location.objects.create(type=district_type, name="Lusaka", slug="402000") clinic = Location.objects.create(type=type, name="Central Clinic", slug="403020") clinic.parent = self.district clinic.save() self.clinic2 = Location.objects.create(type=type, name="Other Clinic", slug="402020") self.clinic2.parent = self.district2 self.clinic2.save() self.clinic_zone= Location.objects.create(type=get_zone_type(), name="child", slug="child", parent=clinic) clinic_worker = self.create_contact(name="clinic_worker", location=clinic, types=[get_clinic_worker_type()]) clinic_worker2 = self.create_contact(name="clinic_worker2", location=clinic, types=[get_clinic_worker_type()]) cba = self.create_contact(name="cba", location=self.clinic_zone, types=[get_cba_type()]) cba2 = self.create_contact(name="cba2", location=self.clinic_zone, types=[get_cba_type()]) active_contacts = Contact.active.all() self.all = [clinic_worker, clinic_worker2, cba, cba2] self.expected_keyword_to_groups = \ {"ALL": [clinic_worker, clinic_worker2, cba, cba2], "CLINIC": [clinic_worker, clinic_worker2], "CBA": [cba, cba2], }
def handle(self, text): b = InputCleaner() if not is_eligible_for_results(self.msg.connection): # essentially checking for an active clinic_worker self.respond(self.INELIGIBLE) return text = text.strip() text = b.remove_double_spaces(text) location = self.msg.contact.location if location.type == const.get_zone_type(): location = location.parent cba = None if text[1:].isdigit() and len(text) >= self.MIN_PHONE_LENGTH: try: cba = \ Contact.active.get(connection__identity__icontains=text, location__parent=location, types=const.get_cba_type()) except Contact.DoesNotExist: self.respond('The phone number %(phone)s does not belong to any' + ' CBA at %(clinic)s. Make sure you typed it ' + 'correctly', phone=text, clinic=location) except Contact.MultipleObjectsReturned: logger.warning("Bug. phone number %s is used by multiple cba's " +"at same clinic" % text) return if not cba: cbas = \ Contact.active.filter(name__icontains=text, location__parent=location, types=const.get_cba_type()) if not cbas: self.respond('The name %(name)s does not belong to any' + ' CBA at %(clinic)s. Make sure you typed it ' + 'correctly', name=text, clinic=location) return if len(cbas) == 1: cba = cbas[0] elif len(cbas) < 5: self.respond("Try sending DEREGISTER <CBA_PHONE_NUMBER>. Which " + "CBA did you mean? %(cbas)s", cbas=' or '.join( cba.name + ":" + cba.default_connection.identity for cba in cbas)) return else: self.respond("There are %(len)s CBA's who's names match %(name)s" + " at %(clinic)s. Try to use the phone number " + "instead", len=len(cbas), name=text, clinic=location.name) return if cba: cba.is_active = False cba.save() self.respond("You have successfully deregistered %(name)s:" + "%(phone)s of zone %(zone)s at %(clinic)s", name=cba.name, phone=cba.default_connection.identity, zone=cba.location.name, clinic=location.name) worker = self.msg.contact for help_admin in Contact.active.filter(is_help_admin=True): OutgoingMessage( help_admin.default_connection, "%s:%s has deregistered %s:%s of zone %s at %s" % (worker.name, worker.default_connection.identity, cba.name, cba.default_connection.identity, cba.location.name, location.name )).send()
def testSendReminders(self): birth = reminders.Event.objects.create(name="Birth", slug="birth", gender="f") birth.appointments.create(name='2 day', num_days=2) birth.appointments.create(name='3 day', num_days=3) birth.appointments.create(name='4 day', num_days=4) clinic = LocationType.objects.create(slug=const.CLINIC_SLUGS[0]) zone = const.get_zone_type() central = Location.objects.create(name='Central Clinic', type=clinic) zone1 = Location.objects.create(name='Zone 1', type=zone, parent=central, slug='zone1') zone2 = Location.objects.create(name='Zone 2', type=zone, parent=central, slug='zone2') patient1 = Contact.objects.create(name='patient 1', location=zone1) patient2 = Contact.objects.create(name='patient 2', location=zone1) patient3 = Contact.objects.create(name='patient 3', location=zone2) # this gets the backend and connection in the db self.runScript(""" cba1 > hello world cba2 > hello world """) # take a break to allow the router thread to catch up; otherwise we # get some bogus messages when they're retrieved below time.sleep(.1) cba_t = const.get_cba_type() cba1_conn = Connection.objects.get(identity="cba1") cba1 = Contact.objects.create(name='cba1', location=zone1) cba1.types.add(cba_t) cba1_conn.contact = cba1 cba1_conn.save() cba2_conn = Connection.objects.get(identity="cba2") cba2 = Contact.objects.create(name='cba2', location=zone2) cba2.types.add(cba_t) cba2_conn.contact = cba2 cba2_conn.save() birth.patient_events.create(patient=patient1, cba_conn=cba1_conn, date=datetime.datetime.today()) birth.patient_events.create(patient=patient2, cba_conn=cba1_conn, date=datetime.datetime.today()) birth.patient_events.create(patient=patient3, cba_conn=cba2_conn, date=datetime.datetime.today()) self.startRouter() tasks.send_notifications(self.router) # just the 1 and two day notifications should go out; # 3 patients x 2 notifications = 6 messages messages = self.receiveAllMessages() expected_messages =\ ['Hello cba1. patient 1 is due for their 2 day clinic appointment. ' 'Please remind this person and ensure they ' 'visit Central Clinic within 3 days.', 'Hello cba1. patient 2 is due for their 2 day clinic appointment. ' 'Please remind this person and ensure they ' 'visit Central Clinic within 3 days.', 'Hello cba2. patient 3 is due for their 2 day clinic appointment. ' 'Please remind this person and ensure they ' 'visit Central Clinic within 3 days.'] self.assertEqual(len(messages), len(expected_messages)) for msg in messages: self.assertTrue(msg.text in expected_messages, msg) sent_notifications = reminders.SentNotification.objects.all() self.assertEqual(sent_notifications.count(), len(expected_messages))
def testSendReminders(self): birth = reminders.Event.objects.create(name="Birth", slug="birth", gender="f") birth.appointments.create(name='2 day', num_days=2) birth.appointments.create(name='3 day', num_days=3) birth.appointments.create(name='4 day', num_days=4) clinic = LocationType.objects.create(slug=const.CLINIC_SLUGS[0]) zone = const.get_zone_type() central = Location.objects.create(name='Central Clinic', type=clinic) zone1 = Location.objects.create(name='Zone 1', type=zone, parent=central, slug='zone1') zone2 = Location.objects.create(name='Zone 2', type=zone, parent=central, slug='zone2') patient1 = Contact.objects.create(name='patient 1', location=zone1) patient2 = Contact.objects.create(name='patient 2', location=zone1) patient3 = Contact.objects.create(name='patient 3', location=zone2) # this gets the backend and connection in the db self.runScript(""" cba1 > hello world cba2 > hello world """) # take a break to allow the router thread to catch up; otherwise we # get some bogus messages when they're retrieved below time.sleep(.1) cba_t = const.get_cba_type() cba1_conn = Connection.objects.get(identity="cba1") cba1 = Contact.objects.create(name='cba1', location=zone1) cba1.types.add(cba_t) cba1_conn.contact = cba1 cba1_conn.save() cba2_conn = Connection.objects.get(identity="cba2") cba2 = Contact.objects.create(name='cba2', location=zone2) cba2.types.add(cba_t) cba2_conn.contact = cba2 cba2_conn.save() birth.patient_events.create(patient=patient1, cba_conn=cba1_conn, date=datetime.datetime.today()) birth.patient_events.create(patient=patient2, cba_conn=cba1_conn, date=datetime.datetime.today()) birth.patient_events.create(patient=patient3, cba_conn=cba2_conn, date=datetime.datetime.today()) self.startRouter() tasks.send_notifications(self.router) # just the 1 and two day notifications should go out; # 3 patients x 2 notifications = 6 messages messages = self.receiveAllMessages() expected_messages =\ ['Hello cba1. patient 1 is due for their 2 day clinic appointment. ' 'Please remind this person and ensure they ' 'visit Central Clinic within 3 days.', 'Hello cba1. patient 2 is due for their 2 day clinic appointment. ' 'Please remind this person and ensure they ' 'visit Central Clinic within 3 days.', 'Hello cba2. patient 3 is due for their 2 day clinic appointment. ' 'Please remind this person and ensure they ' 'visit Central Clinic within 3 days.'] self.assertEqual(len(messages), len(expected_messages)) for msg in messages: self.assertTrue(msg.text in expected_messages, msg) sent_notifications = reminders.SentNotification.objects.all() self.assertEqual(sent_notifications.count(), len(expected_messages))
def handle(self, text): b = InputCleaner() if not is_eligible_for_results(self.msg.connection): # essentially checking for an active clinic_worker self.respond(self.INELIGIBLE) return text = text.strip() text = b.remove_double_spaces(text) worker = self.msg.contact location = worker.location if location.type == const.get_zone_type(): location = location.parent cba = None # we expect phone numbers like +260977123456, 0977123456, 977123456 # (a phone number is unique to each cba at a clinic) if text[1:].isdigit() and len(text) >= self.MIN_PHONE_LENGTH: try: cba = \ Contact.active.get(connection__identity__endswith=text, location__parent=location, types=const.get_cba_type()) except Contact.DoesNotExist: self.respond('The phone number %(phone)s does not belong to any' ' CBA at %(clinic)s. Make sure you typed it ' 'correctly', phone=text, clinic=location) # we do not expect this to happen. phone number is excpected to be # unique (>=9 chars) project wide except Contact.MultipleObjectsReturned: logger.warning("Bug. phone number %s is used by multiple cba's " "at same clinic" % text) self.respond("Sorry %(name)s, the CBA with phone number %(phone)s" " could not be deregistered. This matter will be" " followed up by Support Staff immediately", name=worker.name, phone=text) msg = (self.FOLLOWUP_MESSAGE % (text, worker.name, worker.default_connection.identity, location.name)) self.notify_help_admins(msg) return if not cba: cbas = \ Contact.active.filter(name__icontains=text, location__parent=location, types=const.get_cba_type()) if not cbas: self.respond('The name %(name)s does not belong to any' ' CBA at %(clinic)s. Make sure you typed it ' 'correctly', name=text, clinic=location) return if len(cbas) == 1: cba = cbas[0] elif len(cbas) < 5: self.respond("Try sending REMOVE <CBA_PHONE_NUMBER>. Which " + "CBA did you mean? %(cbas)s", cbas=' or '.join( cba.name + ":" + cba.default_connection.identity for cba in cbas)) return else: self.respond("There are %(len)s CBA's who's names match %(name)s" + " at %(clinic)s. Try to use the phone number " + "instead", len=len(cbas), name=text, clinic=location.name) return if cba: cba.is_active = False cba.save() self.respond("You have successfully deregistered %(name)s:" + "%(phone)s of zone %(zone)s at %(clinic)s", name=cba.name, phone=cba.default_connection.identity, zone=cba.location.name, clinic=location.name) msg = ("%s:%s has deregistered %s:%s of zone %s at %s" % (worker.name, worker.default_connection.identity, cba.name, cba.default_connection.identity, cba.location.name, location.name)) self.notify_help_admins(msg)
def handle(self, text): b = InputCleaner() if not is_eligible_for_results(self.msg.connection): # essentially checking for an active clinic_worker self.respond(self.INELIGIBLE) return text = text.strip() text = b.remove_double_spaces(text) worker = self.msg.contact location = worker.location if location.type == const.get_zone_type(): location = location.parent cba = None # we expect phone numbers like +260977123456, 0977123456, 977123456 # (a phone number is unique to each cba at a clinic) if text[1:].isdigit() and len(text) >= self.MIN_PHONE_LENGTH: try: cba = \ Contact.active.get(connection__identity__endswith=text, location__parent=location, types=const.get_cba_type()) except Contact.DoesNotExist: self.respond( 'The phone number %(phone)s does not belong to any' ' CBA at %(clinic)s. Make sure you typed it ' 'correctly', phone=text, clinic=location) # we do not expect this to happen. phone number is excpected to be # unique (>=9 chars) project wide except Contact.MultipleObjectsReturned: logger.warning( "Bug. phone number %s is used by multiple cba's " "at same clinic" % text) self.respond( "Sorry %(name)s, the CBA with phone number %(phone)s" " could not be deregistered. This matter will be" " followed up by Support Staff immediately", name=worker.name, phone=text) msg = (self.FOLLOWUP_MESSAGE % (text, worker.name, worker.default_connection.identity, location.name)) self.notify_help_admins(msg) return if not cba: cbas = \ Contact.active.filter(name__icontains=text, location__parent=location, types=const.get_cba_type()) if not cbas: self.respond( 'The name %(name)s does not belong to any' ' CBA at %(clinic)s. Make sure you typed it ' 'correctly', name=text, clinic=location) return if len(cbas) == 1: cba = cbas[0] elif len(cbas) < 5: self.respond("Try sending REMOVE <CBA_PHONE_NUMBER>. Which " + "CBA did you mean? %(cbas)s", cbas=' or '.join(cba.name + ":" + cba.default_connection.identity for cba in cbas)) return else: self.respond( "There are %(len)s CBA's who's names match %(name)s" + " at %(clinic)s. Try to use the phone number " + "instead", len=len(cbas), name=text, clinic=location.name) return if cba: cba.is_active = False cba.save() self.respond("You have successfully deregistered %(name)s:" + "%(phone)s of zone %(zone)s at %(clinic)s", name=cba.name, phone=cba.default_connection.identity, zone=cba.location.name, clinic=location.name) msg = ("%s:%s has deregistered %s:%s of zone %s at %s" % (worker.name, worker.default_connection.identity, cba.name, cba.default_connection.identity, cba.location.name, location.name)) self.notify_help_admins(msg)