def setUp(self): self.clear_cache() self.user = self.create_user("tito") self.admin = self.create_user("Administrator") self.org = Org.objects.create(name="Nyaruka Ltd.", timezone="Africa/Kigali", created_by=self.user, modified_by=self.user) self.org.administrators.add(self.admin) self.admin.set_org(self.org) self.org.administrators.add(self.user) self.user.set_org(self.org) self.tel_mtn = Channel.objects.create(org=self.org, name="MTN", channel_type="A", role="SR", address="+250780000000", secret="12345", gcm_id="123", created_by=self.user, modified_by=self.user) self.tel_tigo = Channel.objects.create(org=self.org, name="Tigo", channel_type="A", role="SR", address="+250720000000", secret="23456", gcm_id="234", created_by=self.user, modified_by=self.user) self.tel_bulk = Channel.objects.create(org=self.org, name="Nexmo", channel_type="NX", role="S", parent=self.tel_tigo, secret="34567", created_by=self.user, modified_by=self.user) self.twitter = Channel.objects.create(org=self.org, name="Twitter", channel_type="TT", role="SR", created_by=self.user, modified_by=self.user) # for generating tuples of scheme, path and channel generate_tel_mtn = lambda num: (TEL_SCHEME, "+25078%07d" % (num + 1), self.tel_mtn) generate_tel_tigo = lambda num: (TEL_SCHEME, "+25072%07d" % (num + 1), self.tel_tigo) generate_twitter = lambda num: (TWITTER_SCHEME, "tweep_%d" % (num + 1), self.twitter) self.urn_generators = (generate_tel_mtn, generate_tel_tigo, generate_twitter) self.field_nick = ContactField.get_or_create(self.org, 'nick', 'Nickname', show_in_table=True, value_type=TEXT) self.field_age = ContactField.get_or_create(self.org, 'age', 'Age', show_in_table=True, value_type=DECIMAL)
def setUp(self): self.clear_cache() self.user = self.create_user("tito") self.admin = self.create_user("Administrator") self.org = Org.objects.create(name="Nyaruka Ltd.", timezone="Africa/Kigali", created_by=self.user, modified_by=self.user) self.org.initialize() self.org.administrators.add(self.admin) self.admin.set_org(self.org) self.org.administrators.add(self.user) self.user.set_org(self.org) self.tel_mtn = Channel.objects.create(org=self.org, name="MTN", channel_type="A", role="SR", address="+250780000000", secret="12345", gcm_id="123", created_by=self.user, modified_by=self.user) self.tel_tigo = Channel.objects.create(org=self.org, name="Tigo", channel_type="A", role="SR", address="+250720000000", secret="23456", gcm_id="234", created_by=self.user, modified_by=self.user) self.tel_bulk = Channel.objects.create(org=self.org, name="Nexmo", channel_type="NX", role="S", parent=self.tel_tigo, secret="34567", created_by=self.user, modified_by=self.user) self.twitter = Channel.objects.create(org=self.org, name="Twitter", channel_type="TT", role="SR", created_by=self.user, modified_by=self.user) # for generating tuples of scheme, path and channel generate_tel_mtn = lambda num: (TEL_SCHEME, "+25078%07d" % (num + 1), self.tel_mtn) generate_tel_tigo = lambda num: (TEL_SCHEME, "+25072%07d" % (num + 1), self.tel_tigo) generate_twitter = lambda num: (TWITTER_SCHEME, "tweep_%d" % (num + 1), self.twitter) self.urn_generators = (generate_tel_mtn, generate_tel_tigo, generate_twitter) self.field_nick = ContactField.get_or_create(self.org, 'nick', 'Nickname', show_in_table=True, value_type=TEXT) self.field_age = ContactField.get_or_create(self.org, 'age', 'Age', show_in_table=True, value_type=DECIMAL)
def setUp(self): self.clear_cache() self.user = self.create_user("tito") self.admin = self.create_user("Administrator") self.org = Org.objects.create(name="Nyaruka Ltd.", timezone="Africa/Kigali", created_by=self.user, modified_by=self.user) self.org.initialize() self.org.administrators.add(self.admin) self.admin.set_org(self.org) self.org.administrators.add(self.user) self.user.set_org(self.org) self.tel_mtn = Channel.create(self.org, self.user, 'RW', 'A', name="MTN", address="+250780000000", secret="12345", gcm_id="123") self.tel_tigo = Channel.create(self.org, self.user, 'RW', 'A', name="Tigo", address="+250720000000", secret="23456", gcm_id="234") self.tel_bulk = Channel.create(self.org, self.user, 'RW', 'NX', name="Nexmo", parent=self.tel_tigo) self.twitter = Channel.create(self.org, self.user, None, 'TT', name="Twitter", address="billy_bob") # for generating tuples of scheme, path and channel def generate_tel_mtn(num): return TEL_SCHEME, "+25078%07d" % (num + 1), self.tel_mtn def generate_tel_tigo(num): return TEL_SCHEME, "+25072%07d" % (num + 1), self.tel_tigo def generate_twitter(num): return TWITTER_SCHEME, "tweep_%d" % (num + 1), self.twitter self.urn_generators = (generate_tel_mtn, generate_tel_tigo, generate_twitter) self.field_nick = ContactField.get_or_create(self.org, self.admin, 'nick', 'Nickname', show_in_table=True, value_type=Value.TYPE_TEXT) self.field_age = ContactField.get_or_create(self.org, self.admin, 'age', 'Age', show_in_table=True, value_type=Value.TYPE_DECIMAL)
def test_campaign_events(self): url = reverse('api.v2.campaign_events') self.assertEndpointAccess(url) flow = self.create_flow() reporters = self.create_group("Reporters", [self.joe, self.frank]) registration = ContactField.get_or_create(self.org, self.admin, 'registration', "Registration") campaign1 = Campaign.create(self.org, self.admin, "Reminders", reporters) event1 = CampaignEvent.create_message_event(self.org, self.admin, campaign1, registration, 1, CampaignEvent.UNIT_DAYS, "Don't forget to brush your teeth") campaign2 = Campaign.create(self.org, self.admin, "Notifications", reporters) event2 = CampaignEvent.create_flow_event(self.org, self.admin, campaign2, registration, 6, CampaignEvent.UNIT_HOURS, flow, delivery_hour=12) # create event for another org joined = ContactField.get_or_create(self.org2, self.admin2, 'joined', "Joined On") spammers = ContactGroup.get_or_create(self.org2, self.admin2, "Spammers") spam = Campaign.create(self.org2, self.admin2, "Cool stuff", spammers) CampaignEvent.create_flow_event(self.org2, self.admin2, spam, joined, 6, CampaignEvent.UNIT_HOURS, flow, delivery_hour=12) # no filtering with self.assertNumQueries(NUM_BASE_REQUEST_QUERIES + 4): response = self.fetchJSON(url) self.assertEqual(response.status_code, 200) self.assertEqual(response.json['next'], None) self.assertResultsByUUID(response, [event2, event1]) self.assertEqual(response.json['results'][0], { 'uuid': event2.uuid, 'campaign': {'uuid': campaign2.uuid, 'name': "Notifications"}, 'relative_to': {'key': "registration", 'label': "Registration"}, 'offset': 6, 'unit': 'hours', 'delivery_hour': 12, 'flow': {'uuid': flow.uuid, 'name': "Color Flow"}, 'message': None, 'created_on': format_datetime(event2.created_on) }) # filter by UUID response = self.fetchJSON(url, 'uuid=%s' % event1.uuid) self.assertResultsByUUID(response, [event1]) # filter by campaign name response = self.fetchJSON(url, 'campaign=Reminders') self.assertResultsByUUID(response, [event1]) # filter by campaign UUID response = self.fetchJSON(url, 'campaign=%s' % campaign1.uuid) self.assertResultsByUUID(response, [event1]) # filter by invalid campaign response = self.fetchJSON(url, 'campaign=invalid') self.assertResultsByUUID(response, [])
def test_field_results(self): (c1, c2, c3, c4) = (self.create_contact("Contact1", '0788111111'), self.create_contact("Contact2", '0788222222'), self.create_contact("Contact3", '0788333333'), self.create_contact("Contact4", '0788444444')) # create a gender field that uses strings gender = ContactField.get_or_create(self.org, 'gender', label="Gender", value_type=TEXT) c1.set_field('gender', "Male") c2.set_field('gender', "Female") c3.set_field('gender', "Female") result = Value.get_value_summary(contact_field=gender)[0] self.assertEquals(2, len(result['categories'])) self.assertEquals(3, result['set']) self.assertEquals(2, result['unset']) # this is two as we have the default contact created by our unit tests self.assertFalse(result['open_ended']) self.assertResult(result, 0, "Female", 2) self.assertResult(result, 1, "Male", 1) # create an born field that uses decimals born = ContactField.get_or_create(self.org, 'born', label="Born", value_type=DECIMAL) c1.set_field('born', 1977) c2.set_field('born', 1990) c3.set_field('born', 1977) result = Value.get_value_summary(contact_field=born)[0] self.assertEquals(2, len(result['categories'])) self.assertEquals(3, result['set']) self.assertEquals(2, result['unset']) self.assertFalse(result['open_ended']) self.assertResult(result, 0, "1977", 2) self.assertResult(result, 1, "1990", 1) # ok, state field! state = ContactField.get_or_create(self.org, 'state', label="State", value_type=STATE) c1.set_field('state', "Kigali City") c2.set_field('state', "Kigali City") result = Value.get_value_summary(contact_field=state)[0] self.assertEquals(1, len(result['categories'])) self.assertEquals(2, result['set']) self.assertEquals(3, result['unset']) self.assertResult(result, 0, "1708283", 2) reg_date = ContactField.get_or_create(self.org, 'reg_date', label="Registration Date", value_type=DATETIME) now = timezone.now() c1.set_field('reg_date', now.replace(hour=9)) c2.set_field('reg_date', now.replace(hour=4)) c3.set_field('reg_date', now - timedelta(days=1)) result = Value.get_value_summary(contact_field=reg_date)[0] self.assertEquals(2, len(result['categories'])) self.assertEquals(3, result['set']) self.assertEquals(2, result['unset']) self.assertResult(result, 0, (now - timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0), 1) self.assertResult(result, 1, now.replace(hour=0, minute=0, second=0, microsecond=0), 2)
def test_dst_scheduling(self): # set our timezone to something that honors DST eastern = pytz.timezone("US/Eastern") self.org.timezone = eastern self.org.save() # create our campaign and event campaign = Campaign.create(self.org, self.admin, "Planting Reminders", self.farmers) event = CampaignEvent.create_flow_event( self.org, self.admin, campaign, relative_to=self.planting_date, offset=2, unit="D", flow=self.reminder_flow ) # set the time to something pre-dst (fall back on November 4th at 2am to 1am) self.farmer1.set_field(self.user, "planting_date", "03-11-2029 12:30:00") EventFire.update_campaign_events(campaign) # try changing our field type to something non-date, should throw with self.assertRaises(ValueError): ContactField.get_or_create(self.org, self.admin, "planting_date", value_type=Value.TYPE_TEXT) # we should be scheduled to go off on the 5th at 12:30:10 Eastern fire = EventFire.objects.get() self.assertEqual(5, fire.scheduled.day) self.assertEqual(11, fire.scheduled.month) self.assertEqual(2029, fire.scheduled.year) self.assertEqual(12, fire.scheduled.astimezone(eastern).hour) # assert our offsets are different (we crossed DST) self.assertNotEqual(fire.scheduled.utcoffset(), self.farmer1.get_field_value(self.planting_date).utcoffset()) # the number of hours between these two events should be 49 (two days 1 hour) delta = fire.scheduled - self.farmer1.get_field_value(self.planting_date) self.assertEqual(delta.days, 2) self.assertEqual(delta.seconds, 3600) # spring forward case, this will go across a DST jump forward scenario self.farmer1.set_field(self.user, "planting_date", "10-03-2029 02:30:00") EventFire.update_campaign_events(campaign) fire = EventFire.objects.get() self.assertEqual(12, fire.scheduled.day) self.assertEqual(3, fire.scheduled.month) self.assertEqual(2029, fire.scheduled.year) self.assertEqual(2, fire.scheduled.astimezone(eastern).hour) # assert our offsets changed (we crossed DST) self.assertNotEqual(fire.scheduled.utcoffset(), self.farmer1.get_field_value(self.planting_date).utcoffset()) # delta should be 47 hours exactly delta = fire.scheduled - self.farmer1.get_field_value(self.planting_date) self.assertEqual(delta.days, 1) self.assertEqual(delta.seconds, 82800) # release our campaign event event.release() # should be able to change our field type now ContactField.get_or_create(self.org, self.admin, "planting_date", value_type=Value.TYPE_TEXT)
def form_valid(self, form): try: cleaned_data = form.cleaned_data org = self.request.user.get_org() for key in cleaned_data: if key.startswith('field_'): idx = key[6:] label = cleaned_data["label_%s" % idx] field = cleaned_data[key] show_in_table = cleaned_data["show_%s" % idx] value_type = cleaned_data['type_%s' % idx] if field == '__new_field': if label: analytics.track(self.request.user.username, 'temba.contactfield_created') key = ContactField.make_key(label) ContactField.get_or_create( org, key, label, show_in_table=show_in_table, value_type=value_type) else: if label: ContactField.get_or_create( org, field.key, label, show_in_table=show_in_table, value_type=value_type) else: ContactField.hide_field(org, field.key) if 'HTTP_X_PJAX' not in self.request.META: return HttpResponseRedirect(self.get_success_url()) else: # pragma: no cover return self.render_to_response( self.get_context_data( form=form, success_url=self.get_success_url(), success_script=getattr(self, 'success_script', None))) except IntegrityError as e: # pragma: no cover message = str(e).capitalize() errors = self.form._errors.setdefault( forms.forms.NON_FIELD_ERRORS, forms.util.ErrorList()) errors.append(message) return self.render_to_response( self.get_context_data(form=form))
def update_field_locally(user, contact, key, value, label=None): field = ContactField.get_or_create(contact.org, user, key, label=label) field_uuid = str(field.uuid) if contact.fields is None: contact.fields = {} if not value: value = None if field_uuid in contact.fields: del contact.fields[field_uuid] else: field_dict = contact.serialize_field(field, value) if contact.fields.get(field_uuid) != field_dict: contact.fields[field_uuid] = field_dict # update our JSONB on our contact with connection.cursor() as cursor: if value is None: # delete the field cursor.execute("UPDATE contacts_contact SET fields = fields - %s WHERE id = %s", [field_uuid, contact.id]) else: # update the field cursor.execute( "UPDATE contacts_contact SET fields = COALESCE(fields,'{}'::jsonb) || %s::jsonb WHERE id = %s", [json.dumps({field_uuid: contact.fields[field_uuid]}), contact.id], )
def setUp(self): super(ScheduleTest, self).setUp() self.farmer1 = self.create_contact("Rob Jasper", "+250788111111") self.farmer2 = self.create_contact("Mike Gordon", "+250788222222") self.nonfarmer = self.create_contact("Trey Anastasio", "+250788333333") self.farmers = self.create_group("Farmers", [self.farmer1, self.farmer2]) self.reminder_flow = self.create_flow() self.reminder2_flow = self.create_flow() # create a voice flow to make sure they work too, not a proper voice flow but # sufficient for assuring these flow types show up where they should self.voice_flow = self.create_flow() self.voice_flow.name = 'IVR flow' self.voice_flow.flow_type = 'V' self.voice_flow.save() # create a contact field for our planting date self.planting_date = ContactField.get_or_create( self.org, 'planting_date', "Planting Date") self.admin.groups.add(Group.objects.get(name="Beta"))
def setUp(self): super().setUp() self.farmer1 = self.create_contact("Rob Jasper", "+250788111111") self.farmer2 = self.create_contact("Mike Gordon", "+250788222222", language="spa") self.nonfarmer = self.create_contact("Trey Anastasio", "+250788333333") self.farmers = self.create_group("Farmers", [self.farmer1, self.farmer2]) self.reminder_flow = self.create_flow(name="Reminder Flow") self.reminder2_flow = self.create_flow(name="Planting Reminder") # create a voice flow to make sure they work too, not a proper voice flow but # sufficient for assuring these flow types show up where they should self.voice_flow = self.create_flow(name="IVR flow", flow_type="V") # create a contact field for our planting date self.planting_date = ContactField.get_or_create( self.org, self.admin, "planting_date", "Planting Date", value_type=Value.TYPE_DATETIME)
def setUp(self): super().setUp() self.planting_date = ContactField.get_or_create( self.org, self.admin, "planting_date", "Planting Date", value_type=Value.TYPE_DATETIME ) self.contact = self.create_contact("Ben Haggerty", number="+12065552020") self.contact.set_field(self.admin, "planting_date", "2018-06-23T13:48:12.123456Z") # create a campaign with a message event 1 day after planting date self.farmers = self.create_group("Farmers", [self.contact]) self.campaign = Campaign.create(self.org, self.admin, "Planting Reminders", self.farmers) self.event = CampaignEvent.create_message_event( self.org, self.admin, self.campaign, relative_to=self.planting_date, offset=1, unit="D", message={ "eng": "Hi @(upper(contact.name)) don't forget to plant on @(format_date(contact.planting_date))" }, base_language="eng", )
def save(self): """ Update our contact """ name = self.validated_data.get('name') fields = self.validated_data.get('fields') language = self.validated_data.get('language') # treat empty names as None if not name: name = None changed = [] if self.instance: if self.parsed_urns is not None: self.instance.update_urns(self.user, self.parsed_urns) # update our name and language if name != self.instance.name: self.instance.name = name changed.append('name') else: self.instance = Contact.get_or_create_by_urns(self.org, self.user, name, urns=self.parsed_urns, language=language, force_urn_update=True) # Contact.get_or_create doesn't nullify language so do that here if 'language' in self.validated_data and language is None: self.instance.language = language.lower() if language else None self.instance.save() # save our contact if it changed if changed: self.instance.save(update_fields=changed) # update our fields if fields is not None: for key, value in fields.items(): existing_by_key = ContactField.objects.filter(org=self.org, key__iexact=key, is_active=True).first() if existing_by_key: self.instance.set_field(self.user, existing_by_key.key, value) continue elif self.new_fields and key in self.new_fields: new_field = ContactField.get_or_create(org=self.org, user=self.user, key=regex.sub('[^A-Za-z0-9]+', '_', key).lower(), label=key) self.instance.set_field(self.user, new_field.key, value) # TODO as above, need to get users to stop updating via label existing_by_label = ContactField.get_by_label(self.org, key) if existing_by_label: self.instance.set_field(self.user, existing_by_label.key, value) # update our contact's groups if self.group_objs is not None: self.instance.update_static_groups(self.user, self.group_objs) return self.instance
def save(self): label = self.validated_data.get('label') value_type = self.validated_data.get('value_type') if self.instance: key = self.instance.key else: key = ContactField.make_key(label) return ContactField.get_or_create(self.context['org'], self.context['user'], key, label, value_type=value_type)
def save(self): key = self.validated_data.get('key') label = self.validated_data.get('label') value_type = self.validated_data.get('value_type') return ContactField.get_or_create(self.org, self.user, key, label, value_type=value_type)
def save(self): label = self.validated_data.get("label") value_type = self.validated_data.get("value_type") if self.instance: key = self.instance.key else: key = ContactField.make_key(label) return ContactField.get_or_create(self.context["org"], self.context["user"], key, label, value_type=value_type)
def save(self): label = self.validated_data.get('label') value_type = self.validated_data.get('value_type') if self.instance: key = self.instance.key else: key = ContactField.make_key(label) return ContactField.get_or_create(self.context['org'], self.context['user'], key, label, value_type=value_type)
def save(self): key = self.validated_data.get("key") label = self.validated_data.get("label") value_type = self.validated_data.get("value_type") return ContactField.get_or_create(self.org, self.user, key, label, value_type=value_type)
def save(self): label = self.validated_data.get("label") value_type = self.validated_data.get("value_type") if self.instance: key = self.instance.key else: key = ContactField.make_key(label) return ContactField.get_or_create(self.context["org"], self.context["user"], key, label, value_type=value_type)
def update_field_locally(user, contact, key, value, label=None): org = contact.org field = ContactField.get_or_create(contact.org, user, key, label=label) field_uuid = str(field.uuid) if contact.fields is None: contact.fields = {} if not value: value = None if field_uuid in contact.fields: del contact.fields[field_uuid] else: field_dict = serialize_field_value(contact, field, value) if contact.fields.get(field_uuid) != field_dict: contact.fields[field_uuid] = field_dict # update our JSONB on our contact with connection.cursor() as cursor: if value is None: # delete the field cursor.execute( "UPDATE contacts_contact SET fields = fields - %s WHERE id = %s", [field_uuid, contact.id]) else: # update the field cursor.execute( "UPDATE contacts_contact SET fields = COALESCE(fields,'{}'::jsonb) || %s::jsonb WHERE id = %s", [ json.dumps({field_uuid: contact.fields[field_uuid]}), contact.id ], ) # very simplified version of mailroom's campaign event scheduling events = CampaignEvent.objects.filter( relative_to=field, campaign__group__in=contact.user_groups.all()) for event in events: EventFire.objects.filter(contact=contact, event=event).delete() date_value = parse_datetime(org, value) if date_value: scheduled = date_value + timedelta( **{event_units[event.unit]: event.offset}) if scheduled > timezone.now(): EventFire.objects.create(contact=contact, event=event, scheduled=scheduled)
def form_valid(self, form): try: cleaned_data = form.cleaned_data org = self.request.user.get_org() for key in cleaned_data: if key.startswith('field_'): idx = key[6:] label = cleaned_data["label_%s" % idx] field = cleaned_data[key] show_in_table = cleaned_data["show_%s" % idx] value_type = cleaned_data['type_%s' % idx] if field == '__new_field': if label: analytics.track(self.request.user.username, 'temba.contactfield_created') key = ContactField.make_key(label) ContactField.get_or_create(org, key, label, show_in_table=show_in_table, value_type=value_type) else: if label: ContactField.get_or_create(org, field.key, label, show_in_table=show_in_table, value_type=value_type) else: ContactField.hide_field(org, field.key) if 'HTTP_X_PJAX' not in self.request.META: return HttpResponseRedirect(self.get_success_url()) else: # pragma: no cover return self.render_to_response(self.get_context_data(form=form, success_url=self.get_success_url(), success_script=getattr(self, 'success_script', None))) except IntegrityError as e: # pragma: no cover message = str(e).capitalize() errors = self.form._errors.setdefault(forms.forms.NON_FIELD_ERRORS, forms.utils.ErrorList()) errors.append(message) return self.render_to_response(self.get_context_data(form=form))
def restore_object(self, attrs, instance=None): """ Update our contact field """ if instance: # pragma: no cover raise ValidationError("Invalid operation") org = self.user.get_org() key = attrs.get('key', None) label = attrs.get('label') value_type = attrs.get('value_type') if not key: key = ContactField.make_key(label) return ContactField.get_or_create(org, key, label, value_type=value_type)
def restore_object(self, attrs, instance=None): """ Update our contact field """ if instance: # pragma: no cover raise ValidationError("Invalid operation") org = self.user.get_org() key = attrs.get('key', None) label = attrs.get('label') value_type = attrs.get('value_type') if not key: key = ContactField.make_key(label) return ContactField.get_or_create(org, key, label, value_type=value_type)
def create_event(event_spec, notification, campaign): org = notification.org_dest user = notification.created_by relative_to = ContactField.get_or_create( org, user, key=event_spec["relative_to"]["key"], label=event_spec["relative_to"]["label"], value_type="D", ) # create our message flow for message events if event_spec["event_type"] == CampaignEvent.TYPE_MESSAGE: message = event_spec["message"] base_language = event_spec.get("base_language") if not isinstance(message, dict): try: message = json.loads(message) except ValueError: # if it's not a language dict, turn it into one message = dict(base=message) base_language = "base" event = CampaignEvent.create_message_event( org, user, campaign, relative_to, event_spec["offset"], event_spec["unit"], message, event_spec["delivery_hour"], base_language=base_language, ) event.update_flow_name() else: flow = Flow.objects.filter(org=org, is_active=True, name=event_spec["flow"]["name"]).last() if flow: CampaignEvent.create_flow_event( org, user, campaign, relative_to, event_spec["offset"], event_spec["unit"], flow, event_spec["delivery_hour"], )
def setUp(self): super(CampaignTest, self).setUp() self.farmer1 = self.create_contact("Rob Jasper", "+250788111111") self.farmer2 = self.create_contact("Mike Gordon", "+250788222222") self.nonfarmer = self.create_contact("Trey Anastasio", "+250788333333") self.farmers = self.create_group("Farmers", [self.farmer1, self.farmer2]) self.reminder_flow = self.create_flow() self.reminder2_flow = self.create_flow() # create a voice flow to make sure they work too, not a proper voice flow but # sufficient for assuring these flow types show up where they should self.voice_flow = self.create_flow() self.voice_flow.name = 'IVR flow' self.voice_flow.flow_type = 'V' self.voice_flow.save() # create a contact field for our planting date self.planting_date = ContactField.get_or_create(self.org, self.admin, 'planting_date', "Planting Date")
def get_label(cls, org, field, label=None): from temba.flows.models import get_flow_user # make sure this field exists if field == "name": label = "Contact Name" elif field == "first_name": label = "First Name" elif field == "tel_e164": label = "Phone Number" elif field in ContactURN.CONTEXT_KEYS_TO_SCHEME.keys(): label = str(ContactURN.CONTEXT_KEYS_TO_LABEL[field]) else: contact_field = ContactField.user_fields.filter(org=org, key=field).first() if not contact_field: contact_field = ContactField.get_or_create(org, get_flow_user(org), field, label) label = contact_field.label return label
def test_fields(self): url = reverse('api.v2.fields') self.assertEndpointAccess(url) ContactField.get_or_create(self.org, self.admin, 'nick_name', "Nick Name") ContactField.get_or_create(self.org, self.admin, 'registered', "Registered On", value_type=Value.TYPE_DATETIME) ContactField.get_or_create(self.org2, self.admin2, 'not_ours', "Something Else") # no filtering with self.assertNumQueries(NUM_BASE_REQUEST_QUERIES + 1): response = self.fetchJSON(url) self.assertEqual(response.status_code, 200) self.assertEqual(response.json['next'], None) self.assertEqual(response.json['results'], [{ 'key': 'registered', 'label': "Registered On", 'value_type': "datetime" }, { 'key': 'nick_name', 'label': "Nick Name", 'value_type': "text" }]) # filter by key response = self.fetchJSON(url, 'key=nick_name') self.assertEqual(response.json['results'], [{ 'key': 'nick_name', 'label': "Nick Name", 'value_type': "text" }])
def test_fields(self): url = reverse('api.v2.fields') self.assertEndpointAccess(url) ContactField.get_or_create(self.org, self.admin, 'nick_name', "Nick Name") ContactField.get_or_create(self.org, self.admin, 'registered', "Registered On", value_type=Value.TYPE_DATETIME) ContactField.get_or_create(self.org2, self.admin2, 'not_ours', "Something Else") # no filtering with self.assertNumQueries(NUM_BASE_REQUEST_QUERIES + 1): response = self.fetchJSON(url) self.assertEqual(response.status_code, 200) self.assertEqual(response.json['next'], None) self.assertEqual(response.json['results'], [ {'key': 'registered', 'label': "Registered On", 'value_type': "datetime"}, {'key': 'nick_name', 'label': "Nick Name", 'value_type': "text"} ]) # filter by key response = self.fetchJSON(url, 'key=nick_name') self.assertEqual(response.json['results'], [{'key': 'nick_name', 'label': "Nick Name", 'value_type': "text"}])
def import_campaigns(cls, exported_json, org, user, same_site=False): """ Import campaigns from our export file """ from temba.orgs.models import EARLIEST_IMPORT_VERSION if Flow.is_before_version( exported_json.get("version", "0"), EARLIEST_IMPORT_VERSION): # pragma: needs cover raise ValueError( _("Unknown version (%s)" % exported_json.get("version", 0))) if "campaigns" in exported_json: for campaign_spec in exported_json["campaigns"]: name = campaign_spec["name"] campaign = None group = None # first check if we have the objects by id if same_site: group = ContactGroup.user_groups.filter( uuid=campaign_spec["group"]["uuid"], org=org).first() if group: # pragma: needs cover group.name = campaign_spec["group"]["name"] group.save() campaign = Campaign.objects.filter( org=org, uuid=campaign_spec["uuid"]).first() if campaign: # pragma: needs cover campaign.name = Campaign.get_unique_name( org, name, ignore=campaign) campaign.save() # fall back to lookups by name if not group: group = ContactGroup.get_user_group( org, campaign_spec["group"]["name"]) if not campaign: campaign = Campaign.objects.filter(org=org, name=name).first() # all else fails, create the objects from scratch if not group: group = ContactGroup.create_static( org, user, campaign_spec["group"]["name"]) if not campaign: campaign_name = Campaign.get_unique_name(org, name) campaign = Campaign.create(org, user, campaign_name, group) else: campaign.group = group campaign.save() # release all of our events, we'll recreate these for event in campaign.events.all(): event.release() # fill our campaign with events for event_spec in campaign_spec["events"]: relative_to = ContactField.get_or_create( org, user, key=event_spec["relative_to"]["key"], label=event_spec["relative_to"]["label"], value_type="D", ) # create our message flow for message events if event_spec["event_type"] == CampaignEvent.TYPE_MESSAGE: message = event_spec["message"] base_language = event_spec.get("base_language") if not isinstance(message, dict): try: message = json.loads(message) except ValueError: # if it's not a language dict, turn it into one message = dict(base=message) base_language = "base" event = CampaignEvent.create_message_event( org, user, campaign, relative_to, event_spec["offset"], event_spec["unit"], message, event_spec["delivery_hour"], base_language=base_language, ) event.update_flow_name() else: flow = Flow.objects.filter( org=org, is_active=True, uuid=event_spec["flow"]["uuid"]).first() if flow: CampaignEvent.create_flow_event( org, user, campaign, relative_to, event_spec["offset"], event_spec["unit"], flow, event_spec["delivery_hour"], ) # update our scheduled events for this campaign EventFire.update_campaign_events(campaign)
def import_campaigns(cls, exported_json, org, user, same_site=False): """ Import campaigns from our export file """ from temba.orgs.models import EARLIEST_IMPORT_VERSION if Flow.is_before_version( exported_json.get('version', "0"), EARLIEST_IMPORT_VERSION): # pragma: needs cover raise ValueError( _("Unknown version (%s)" % exported_json.get('version', 0))) if 'campaigns' in exported_json: for campaign_spec in exported_json['campaigns']: name = campaign_spec['name'] campaign = None group = None # first check if we have the objects by id if same_site: group = ContactGroup.user_groups.filter( uuid=campaign_spec['group']['uuid'], org=org).first() if group: # pragma: needs cover group.name = campaign_spec['group']['name'] group.save() campaign = Campaign.objects.filter( org=org, uuid=campaign_spec['uuid']).first() if campaign: # pragma: needs cover campaign.name = Campaign.get_unique_name( org, name, ignore=campaign) campaign.save() # fall back to lookups by name if not group: group = ContactGroup.get_user_group( org, campaign_spec['group']['name']) if not campaign: campaign = Campaign.objects.filter(org=org, name=name).first() # all else fails, create the objects from scratch if not group: group = ContactGroup.create_static( org, user, campaign_spec['group']['name']) if not campaign: campaign_name = Campaign.get_unique_name(org, name) campaign = Campaign.create(org, user, campaign_name, group) else: campaign.group = group campaign.save() # we want to nuke old single message flows for event in campaign.events.all(): if event.flow.flow_type == Flow.MESSAGE: event.flow.release() # and all of the events, we'll recreate these campaign.events.all().delete() # fill our campaign with events for event_spec in campaign_spec['events']: relative_to = ContactField.get_or_create( org, user, key=event_spec['relative_to']['key'], label=event_spec['relative_to']['label']) # create our message flow for message events if event_spec['event_type'] == CampaignEvent.TYPE_MESSAGE: message = event_spec['message'] base_language = event_spec.get('base_language') if not isinstance(message, dict): try: message = json.loads(message) except ValueError: # if it's not a language dict, turn it into one message = dict(base=message) base_language = 'base' event = CampaignEvent.create_message_event( org, user, campaign, relative_to, event_spec['offset'], event_spec['unit'], message, event_spec['delivery_hour'], base_language=base_language) event.update_flow_name() else: flow = Flow.objects.filter( org=org, is_active=True, uuid=event_spec['flow']['uuid']).first() if flow: CampaignEvent.create_flow_event( org, user, campaign, relative_to, event_spec['offset'], event_spec['unit'], flow, event_spec['delivery_hour']) # update our scheduled events for this campaign EventFire.update_campaign_events(campaign)
def import_campaigns(cls, exported_json, org, user, same_site=False): """ Import campaigns from our export file """ from temba.orgs.models import EARLIEST_IMPORT_VERSION if exported_json.get("version", 0) < EARLIEST_IMPORT_VERSION: raise ValueError(_("Unknown version (%s)" % exported_json.get("version", 0))) if "campaigns" in exported_json: for campaign_spec in exported_json["campaigns"]: name = campaign_spec["name"] campaign = None group = None # first check if we have the objects by id if same_site: group = ContactGroup.user_groups.filter( id=campaign_spec["group"]["id"], org=org, is_active=True ).first() if group: group.name = campaign_spec["group"]["name"] group.save() campaign = Campaign.objects.filter(org=org, id=campaign_spec["id"]).first() if campaign: campaign.name = Campaign.get_unique_name(org, name, ignore=campaign) campaign.save() # fall back to lookups by name if not group: group = ContactGroup.user_groups.filter(name=campaign_spec["group"]["name"], org=org).first() if not campaign: campaign = Campaign.objects.filter(org=org, name=name).first() # all else fails, create the objects from scratch if not group: group = ContactGroup.create(org, user, campaign_spec["group"]["name"]) if not campaign: campaign_name = Campaign.get_unique_name(org, name) campaign = Campaign.create(org, user, campaign_name, group) else: campaign.group = group campaign.save() # we want to nuke old single message flows for event in campaign.events.all(): if event.flow.flow_type == Flow.MESSAGE: event.flow.delete() # and all of the events, we'll recreate these campaign.events.all().delete() # fill our campaign with events for event_spec in campaign_spec["events"]: relative_to = ContactField.get_or_create( org, key=event_spec["relative_to"]["key"], label=event_spec["relative_to"]["label"] ) # create our message flow for message events if event_spec["event_type"] == MESSAGE_EVENT: event = CampaignEvent.create_message_event( org, user, campaign, relative_to, event_spec["offset"], event_spec["unit"], event_spec["message"], event_spec["delivery_hour"], ) event.update_flow_name() else: flow = Flow.objects.filter(org=org, id=event_spec["flow"]["id"]).first() if flow: CampaignEvent.create_flow_event( org, user, campaign, relative_to, event_spec["offset"], event_spec["unit"], flow, event_spec["delivery_hour"], ) # update our scheduled events for this campaign EventFire.update_campaign_events(campaign)
def save(self): """ Update our contact """ name = self.validated_data.get("name") fields = self.validated_data.get("fields") language = self.validated_data.get("language") # treat empty names as None if not name: name = None changed = [] if self.instance: if self.parsed_urns is not None: self.instance.update_urns(self.user, self.parsed_urns) # update our name and language if name != self.instance.name: self.instance.name = name changed.append("name") else: self.instance = Contact.get_or_create_by_urns( self.org, self.user, name, urns=self.parsed_urns, language=language, force_urn_update=True ) # Contact.get_or_create doesn't nullify language so do that here if "language" in self.validated_data and language is None: self.instance.language = language.lower() if language else None changed.append("language") # save our contact if it changed if changed: self.instance.save(update_fields=changed, handle_update=True) # update our fields if fields is not None: for key, value in fields.items(): existing_by_key = ContactField.user_fields.filter( org=self.org, key__iexact=key, is_active=True ).first() if existing_by_key: self.instance.set_field(self.user, existing_by_key.key, value) continue elif self.new_fields and key in self.new_fields: new_field = ContactField.get_or_create( org=self.org, user=self.user, key=regex.sub("[^A-Za-z0-9]+", "_", key).lower(), label=key ) self.instance.set_field(self.user, new_field.key, value) # TODO as above, need to get users to stop updating via label existing_by_label = ContactField.get_by_label(self.org, key) if existing_by_label: self.instance.set_field(self.user, existing_by_label.key, value) # update our contact's groups if self.group_objs is not None: self.instance.update_static_groups(self.user, self.group_objs) return self.instance
def restore_object(self, attrs, instance=None): """ Create or update our campaign """ if instance: # pragma: no cover raise ValidationError("Invalid operation") org = self.user.get_org() # parse our arguments message = attrs.get('message', None) flow = attrs.get('flow', None) if not message and not flow: raise ValidationError("Must specify either a flow or a message for the event") if message and flow: raise ValidationError("You cannot set both a flow and a message on an event, it must be only one") campaign_id = attrs.get('campaign', None) event_id = attrs.get('event', None) if not campaign_id and not event_id: raise ValidationError("You must specify either a campaign to create a new event, or an event to update") offset = attrs.get('offset') unit = attrs.get('unit') delivery_hour = attrs.get('delivery_hour') relative_to = attrs.get('relative_to') # load our contact field existing_field = ContactField.objects.filter(label=relative_to, org=org, is_active=True) if not existing_field: relative_to_field = ContactField.get_or_create(org, ContactField.make_key(relative_to), relative_to) else: relative_to_field = existing_field[0] if 'event' in attrs: event = CampaignEvent.objects.get(pk=attrs['event'], is_active=True, campaign__org=org) # we are being set to a flow if 'flow' in attrs: flow = Flow.objects.get(pk=attrs['flow'], is_active=True, org=org) event.flow = flow event.event_type = FLOW_EVENT event.message = None # we are being set to a message else: event.message = attrs['message'] # if we aren't currently a message event, we need to create our hidden message flow if event.event_type != MESSAGE_EVENT: event.flow = Flow.create_single_message(org, self.user, event.message) event.event_type = MESSAGE_EVENT # otherwise, we can just update that flow else: # set our single message on our flow event.flow.update_single_message_flow(message=attrs['message']) # update our other attributes event.offset = offset event.unit = unit event.delivery_hour = delivery_hour event.relative_to = relative_to_field event.save() event.update_flow_name() else: campaign = Campaign.objects.get(pk=attrs['campaign'], is_active=True, org=org) event_type = MESSAGE_EVENT if 'flow' in attrs: flow = Flow.objects.get(pk=attrs['flow'], is_active=True, org=org) event_type = FLOW_EVENT else: flow = Flow.create_single_message(org, self.user, message) event = CampaignEvent.objects.create(campaign=campaign, relative_to=relative_to_field, offset=offset, unit=unit, event_type=event_type, flow=flow, message=message, created_by=self.user, modified_by=self.user) event.update_flow_name() return event
def test_category_results(self): self.setup_color_gender_flow() # create a state field: # assign c1 and c2 to Kigali ContactField.get_or_create(self.org, self.admin, 'state', label="State", value_type=Value.TYPE_STATE) ContactField.get_or_create(self.org, self.admin, 'district', label="District", value_type=Value.TYPE_DISTRICT) self.c1.set_field(self.user, 'state', "Kigali City") self.c1.set_field(self.user, 'district', "Nyarugenge") self.c2.set_field(self.user, 'state', "Kigali City") self.c2.set_field(self.user, 'district', "Nyarugenge") self.run_color_gender_flow(self.c1, "red", "male", "16") self.run_color_gender_flow(self.c2, "blue", "female", "19") self.run_color_gender_flow(self.c3, "green", "male", "75") self.run_color_gender_flow(self.c4, "maroon", "female", "50") # create a group of the women ladies = self.create_group("Ladies", [self.c2, self.c4]) # get our rulesets color = RuleSet.objects.get(flow=self.flow, label="Color") gender = RuleSet.objects.get(flow=self.flow, label="Gender") age = RuleSet.objects.get(flow=self.flow, label="Age") # fetch our results through the view self.login(self.admin) response = self.client.get( reverse('flows.ruleset_results', args=[color.pk])) response = response.json() categories = response['results'][0]['categories'] self.assertEqual('Red', categories[0]['label']) self.assertEqual('Blue', categories[1]['label']) self.assertEqual('Green', categories[2]['label']) self.assertEqual(2, categories[0]['count']) self.assertEqual(1, categories[1]['count']) self.assertEqual(1, categories[2]['count']) # categories should be in the same order as our rules, should have correct counts result = Value.get_value_summary(ruleset=color)[0] self.assertEquals(3, len(result['categories'])) self.assertFalse(result['open_ended']) self.assertResult(result, 0, "Red", 2) self.assertResult(result, 1, "Blue", 1) self.assertResult(result, 2, "Green", 1) # check our age category as well result = Value.get_value_summary(ruleset=age)[0] self.assertEquals(3, len(result['categories'])) self.assertFalse(result['open_ended']) self.assertResult(result, 0, "Child", 1) self.assertResult(result, 1, "Adult", 2) self.assertResult(result, 2, "Senior", 1) # and our gender categories result = Value.get_value_summary(ruleset=gender)[0] self.assertEquals(2, len(result['categories'])) self.assertFalse(result['open_ended']) self.assertResult(result, 0, "Male", 2) self.assertResult(result, 1, "Female", 2) # now filter the results and only get responses by men result = Value.get_value_summary( ruleset=color, filters=[dict(ruleset=gender.pk, categories=["Male"])])[0] self.assertResult(result, 0, "Red", 1) self.assertResult(result, 1, "Blue", 0) self.assertResult(result, 2, "Green", 1) # what about men that are adults? result = Value.get_value_summary(ruleset=color, filters=[ dict(ruleset=gender.pk, categories=["Male"]), dict(ruleset=age.pk, categories=["Adult"]) ])[0] self.assertResult(result, 0, "Red", 0) self.assertResult(result, 1, "Blue", 0) self.assertResult(result, 2, "Green", 0) # union of all genders result = Value.get_value_summary( ruleset=color, filters=[ dict(ruleset=gender.pk, categories=["Male", "Female"]), dict(ruleset=age.pk, categories=["Adult"]) ])[0] self.assertResult(result, 0, "Red", 1) self.assertResult(result, 1, "Blue", 1) self.assertResult(result, 2, "Green", 0) # just women adults by group result = Value.get_value_summary(ruleset=color, filters=[ dict(groups=[ladies.pk]), dict(ruleset=age.pk, categories="Adult") ])[0] self.assertResult(result, 0, "Red", 1) self.assertResult(result, 1, "Blue", 1) self.assertResult(result, 2, "Green", 0) # remove one of the women from the group ladies.update_contacts(self.user, [self.c2], False) # get a new summary result = Value.get_value_summary(ruleset=color, filters=[ dict(groups=[ladies.pk]), dict(ruleset=age.pk, categories="Adult") ])[0] self.assertResult(result, 0, "Red", 1) self.assertResult(result, 1, "Blue", 0) self.assertResult(result, 2, "Green", 0) # ok, back in she goes ladies.update_contacts(self.user, [self.c2], True) # do another run for contact 1 self.run_color_gender_flow(self.c1, "blue", "male", "16") # totals should reflect the new value, not the old result = Value.get_value_summary(ruleset=color)[0] self.assertResult(result, 0, "Red", 1) self.assertResult(result, 1, "Blue", 2) self.assertResult(result, 2, "Green", 1) # what if we do a partial run? self.send_message(self.flow, "red", contact=self.c1, restart_participants=True) # should change our male/female breakdown since c1 now no longer has a gender result = Value.get_value_summary(ruleset=gender)[0] self.assertEquals(2, len(result['categories'])) self.assertResult(result, 0, "Male", 1) self.assertResult(result, 1, "Female", 2) # back to a full flow self.run_color_gender_flow(self.c1, "blue", "male", "16") # ok, now segment by gender result = Value.get_value_summary(ruleset=color, filters=[], segment=dict( ruleset=gender.pk, categories=["Male", "Female"])) male_result = result[0] self.assertResult(male_result, 0, "Red", 0) self.assertResult(male_result, 1, "Blue", 1) self.assertResult(male_result, 2, "Green", 1) female_result = result[1] self.assertResult(female_result, 0, "Red", 1) self.assertResult(female_result, 1, "Blue", 1) self.assertResult(female_result, 2, "Green", 0) # segment by gender again, but use the contact field to do so result = Value.get_value_summary(ruleset=color, filters=[], segment=dict( contact_field="Gender", values=["MALE", "Female"])) male_result = result[0] self.assertResult(male_result, 0, "Red", 0) self.assertResult(male_result, 1, "Blue", 1) self.assertResult(male_result, 2, "Green", 1) female_result = result[1] self.assertResult(female_result, 0, "Red", 1) self.assertResult(female_result, 1, "Blue", 1) self.assertResult(female_result, 2, "Green", 0) # add in a filter at the same time result = Value.get_value_summary( ruleset=color, filters=[dict(ruleset=color.pk, categories=["Blue"])], segment=dict(ruleset=gender.pk, categories=["Male", "Female"])) male_result = result[0] self.assertResult(male_result, 0, "Red", 0) self.assertResult(male_result, 1, "Blue", 1) self.assertResult(male_result, 2, "Green", 0) female_result = result[1] self.assertResult(female_result, 0, "Red", 0) self.assertResult(female_result, 1, "Blue", 1) self.assertResult(female_result, 2, "Green", 0) # ok, try segmenting by location instead result = Value.get_value_summary(ruleset=color, segment=dict(location="State")) eastern_result = result[0] self.assertEquals('171591', eastern_result['boundary']) self.assertEquals('Eastern Province', eastern_result['label']) self.assertResult(eastern_result, 0, "Red", 0) self.assertResult(eastern_result, 1, "Blue", 0) self.assertResult(eastern_result, 2, "Green", 0) kigali_result = result[1] self.assertEquals('1708283', kigali_result['boundary']) self.assertEquals('Kigali City', kigali_result['label']) self.assertResult(kigali_result, 0, "Red", 0) self.assertResult(kigali_result, 1, "Blue", 2) self.assertResult(kigali_result, 2, "Green", 0) # updating state location leads to updated data self.c2.set_field(self.user, 'state', "Eastern Province") result = Value.get_value_summary(ruleset=color, segment=dict(location="State")) eastern_result = result[0] self.assertEquals('171591', eastern_result['boundary']) self.assertEquals('Eastern Province', eastern_result['label']) self.assertResult(eastern_result, 0, "Red", 0) self.assertResult(eastern_result, 1, "Blue", 1) self.assertResult(eastern_result, 2, "Green", 0) kigali_result = result[1] self.assertEquals('1708283', kigali_result['boundary']) self.assertEquals('Kigali City', kigali_result['label']) self.assertResult(kigali_result, 0, "Red", 0) self.assertResult(kigali_result, 1, "Blue", 1) self.assertResult(kigali_result, 2, "Green", 0) # segment by district instead result = Value.get_value_summary(ruleset=color, segment=dict(parent="1708283", location="District")) # only on district in kigali self.assertEquals(1, len(result)) kigali_result = result[0] self.assertEquals('3963734', kigali_result['boundary']) self.assertEquals('Nyarugenge', kigali_result['label']) self.assertResult(kigali_result, 0, "Red", 0) self.assertResult(kigali_result, 1, "Blue", 2) self.assertResult(kigali_result, 2, "Green", 0) # do a sanity check on our choropleth view self.login(self.admin) response = self.client.get( reverse('flows.ruleset_choropleth', args=[color.pk]) + "?_format=json&boundary=" + self.org.country.osm_id) # response should be valid json response = response.json() # should have breaks self.assertTrue('breaks' in response) # should have two categories, Blue and Others self.assertEquals(2, len(response['categories'])) self.assertEquals("Blue", response['categories'][0]) self.assertEquals("Others", response['categories'][1]) # assert our kigali result kigali_result = response['scores']['1708283'] self.assertEquals(1, kigali_result['score']) self.assertEquals("Kigali City", kigali_result['name']) self.assertEquals("Blue", kigali_result['results'][0]['label']) self.assertEquals("Others", kigali_result['results'][1]['label']) self.assertEquals(1, kigali_result['results'][0]['count']) self.assertEquals(0, kigali_result['results'][1]['count']) self.assertEquals(100, kigali_result['results'][0]['percentage']) self.assertEquals(0, kigali_result['results'][1]['percentage']) with patch('temba.values.models.Value.get_value_summary') as mock: mock.return_value = [] response = self.client.get( reverse('flows.ruleset_choropleth', args=[color.pk]) + "?_format=json&boundary=" + self.org.country.osm_id) # response should be valid json response = response.json() # should have two categories, Blue and Others self.assertEquals(2, len(response['categories'])) self.assertEquals("", response['categories'][0]) self.assertEquals("", response['categories'][1]) # all counts and percentage are 0 self.assertEquals(0, response['totals']['count']) self.assertEquals(0, response['totals']['results'][0]['count']) self.assertEquals(0, response['totals']['results'][0]['percentage']) self.assertEquals(0, response['totals']['results'][1]['count']) self.assertEquals(0, response['totals']['results'][1]['percentage']) # and empty string labels self.assertEquals("", response['totals']['results'][0]['label']) self.assertEquals("", response['totals']['results'][1]['label']) # also check our analytics view response = self.client.get(reverse('flows.ruleset_analytics')) # make sure we have only one flow in it flows = json.loads(response.context['flows']) self.assertEquals(1, len(flows)) self.assertEquals(3, len(flows[0]['rules']))
def has_whatsapp_timestamp_contactfield(org): return ContactField.get_or_create(org, user=org.administrators.first(), key=HAS_WHATSAPP_TIMESTAMP_KEY, value_type=Value.TYPE_DATETIME)
def import_campaigns(cls, org, user, campaign_defs, same_site=False) -> List: """ Import campaigns from a list of exported campaigns """ imported = [] for campaign_def in campaign_defs: name = campaign_def[Campaign.EXPORT_NAME] campaign = None group = None # first check if we have the objects by UUID if same_site: group = ContactGroup.user_groups.filter( uuid=campaign_def[Campaign.EXPORT_GROUP]["uuid"], org=org).first() if group: # pragma: needs cover group.name = campaign_def[Campaign.EXPORT_GROUP]["name"] group.save() campaign = Campaign.objects.filter( org=org, uuid=campaign_def[Campaign.EXPORT_UUID]).first() if campaign: # pragma: needs cover campaign.name = Campaign.get_unique_name(org, name, ignore=campaign) campaign.save() # fall back to lookups by name if not group: group = ContactGroup.get_user_group_by_name( org, campaign_def[Campaign.EXPORT_GROUP]["name"]) if not campaign: campaign = Campaign.objects.filter(org=org, name=name).first() # all else fails, create the objects from scratch if not group: group = ContactGroup.create_static( org, user, campaign_def[Campaign.EXPORT_GROUP]["name"]) if not campaign: campaign_name = Campaign.get_unique_name(org, name) campaign = Campaign.create(org, user, campaign_name, group) else: campaign.group = group campaign.save() # deactivate all of our events, we'll recreate these for event in campaign.events.all(): event.release() # fill our campaign with events for event_spec in campaign_def[Campaign.EXPORT_EVENTS]: field_key = event_spec["relative_to"]["key"] if field_key == "created_on": relative_to = ContactField.system_fields.filter( org=org, key=field_key).first() else: relative_to = ContactField.get_or_create( org, user, key=field_key, label=event_spec["relative_to"]["label"], value_type="D") start_mode = event_spec.get("start_mode", CampaignEvent.MODE_INTERRUPT) # create our message flow for message events if event_spec["event_type"] == CampaignEvent.TYPE_MESSAGE: message = event_spec["message"] base_language = event_spec.get("base_language") if not isinstance(message, dict): try: message = json.loads(message) except ValueError: # if it's not a language dict, turn it into one message = dict(base=message) base_language = "base" event = CampaignEvent.create_message_event( org, user, campaign, relative_to, event_spec["offset"], event_spec["unit"], message, event_spec["delivery_hour"], base_language=base_language, start_mode=start_mode, ) event.update_flow_name() else: flow = Flow.objects.filter( org=org, is_active=True, is_system=False, uuid=event_spec["flow"]["uuid"]).first() if flow: CampaignEvent.create_flow_event( org, user, campaign, relative_to, event_spec["offset"], event_spec["unit"], flow, event_spec["delivery_hour"], start_mode=start_mode, ) imported.append(campaign) return imported
def restore_object(self, attrs, instance=None): """ Create or update our campaign """ if instance: # pragma: no cover raise ValidationError("Invalid operation") org = self.user.get_org() # parse our arguments message = attrs.get('message', None) flow = attrs.get('flow', None) if not message and not flow: raise ValidationError("Must specify either a flow or a message for the event") if message and flow: raise ValidationError("You cannot set both a flow and a message on an event, it must be only one") campaign_id = attrs.get('campaign', None) event_id = attrs.get('event', None) if not campaign_id and not event_id: raise ValidationError("You must specify either a campaign to create a new event, or an event to update") offset = attrs.get('offset') unit = attrs.get('unit') delivery_hour = attrs.get('delivery_hour') relative_to = attrs.get('relative_to') # load our contact field existing_field = ContactField.objects.filter(label=relative_to, org=org, is_active=True) if not existing_field: relative_to_field = ContactField.get_or_create(org, ContactField.make_key(relative_to), relative_to) else: relative_to_field = existing_field[0] if 'event' in attrs: event = CampaignEvent.objects.get(pk=attrs['event'], is_active=True, campaign__org=org) # we are being set to a flow if 'flow' in attrs: flow = Flow.objects.get(pk=attrs['flow'], is_active=True, org=org) event.flow = flow event.event_type = FLOW_EVENT event.message = None # we are being set to a message else: event.message = attrs['message'] # if we aren't currently a message event, we need to create our hidden message flow if event.event_type != MESSAGE_EVENT: event.flow = CampaignEvent.create_single_message_flow(org, self.user, event.message) event.event_type = MESSAGE_EVENT # otherwise, we can just update that flow else: # set our single message on our flow event.flow.update_single_message_flow(message=attrs['message']) # update our other attributes event.offset = offset event.unit = unit event.delivery_hour = delivery_hour event.relative_to = relative_to_field event.save() event.update_flow_name() else: campaign = Campaign.objects.get(pk=attrs['campaign'], is_active=True, org=org) event_type = MESSAGE_EVENT if 'flow' in attrs: flow = Flow.objects.get(pk=attrs['flow'], is_active=True, org=org) event_type = FLOW_EVENT else: flow = CampaignEvent.create_single_message_flow(org, self.user, message) event = CampaignEvent.objects.create(campaign=campaign, relative_to=relative_to_field, offset=offset, unit=unit, event_type=event_type, flow=flow, message=message, created_by=self.user, modified_by=self.user) event.update_flow_name() return event
def import_campaigns(cls, exported_json, org, user, same_site=False): """ Import campaigns from our export file """ from temba.orgs.models import EARLIEST_IMPORT_VERSION if exported_json.get('version', 0) < EARLIEST_IMPORT_VERSION: raise ValueError(_("Unknown version (%s)" % exported_json.get('version', 0))) if 'campaigns' in exported_json: for campaign_spec in exported_json['campaigns']: name = campaign_spec['name'] campaign = None group = None # first check if we have the objects by id if same_site: group = ContactGroup.user_groups.filter(uuid=campaign_spec['group']['uuid'], org=org).first() if group: group.name = campaign_spec['group']['name'] group.save() campaign = Campaign.objects.filter(org=org, uuid=campaign_spec['uuid']).first() if campaign: campaign.name = Campaign.get_unique_name(org, name, ignore=campaign) campaign.save() # fall back to lookups by name if not group: group = ContactGroup.get_user_group(org, campaign_spec['group']['name']) if not campaign: campaign = Campaign.objects.filter(org=org, name=name).first() # all else fails, create the objects from scratch if not group: group = ContactGroup.create_static(org, user, campaign_spec['group']['name']) if not campaign: campaign_name = Campaign.get_unique_name(org, name) campaign = Campaign.create(org, user, campaign_name, group) else: campaign.group = group campaign.save() # we want to nuke old single message flows for event in campaign.events.all(): if event.flow.flow_type == Flow.MESSAGE: event.flow.release() # and all of the events, we'll recreate these campaign.events.all().delete() # fill our campaign with events for event_spec in campaign_spec['events']: relative_to = ContactField.get_or_create(org, user, key=event_spec['relative_to']['key'], label=event_spec['relative_to']['label']) # create our message flow for message events if event_spec['event_type'] == CampaignEvent.TYPE_MESSAGE: event = CampaignEvent.create_message_event(org, user, campaign, relative_to, event_spec['offset'], event_spec['unit'], event_spec['message'], event_spec['delivery_hour']) event.update_flow_name() else: flow = Flow.objects.filter(org=org, is_active=True, uuid=event_spec['flow']['uuid']).first() if flow: CampaignEvent.create_flow_event(org, user, campaign, relative_to, event_spec['offset'], event_spec['unit'], flow, event_spec['delivery_hour']) # update our scheduled events for this campaign EventFire.update_campaign_events(campaign)
def save(self): key = self.validated_data.get("key") label = self.validated_data.get("label") value_type = self.validated_data.get("value_type") return ContactField.get_or_create(self.org, self.user, key, label, value_type=value_type)
def import_campaigns(cls, exported_json, org, user, site=None): """ Import campaigns from our export file """ from temba.orgs.models import EARLIEST_IMPORT_VERSION if exported_json.get('version', 0) < EARLIEST_IMPORT_VERSION: raise ValueError( _("Unknown version (%s)" % exported_json.get('version', 0))) if 'campaigns' in exported_json: for campaign_spec in exported_json['campaigns']: name = campaign_spec['name'] campaign = None group = None # first check if we have the objects by id if site and site == exported_json.get('site', None): group = ContactGroup.user_groups.filter( id=campaign_spec['group']['id'], org=org, is_active=True).first() if group: group.name = campaign_spec['group']['name'] group.save() campaign = Campaign.objects.filter( org=org, id=campaign_spec['id']).first() if campaign: campaign.name = Campaign.get_unique_name( name, org, ignore=campaign) campaign.save() # fall back to lookups by name if not group: group = ContactGroup.user_groups.filter( name=campaign_spec['group']['name'], org=org).first() if not campaign: campaign = Campaign.objects.filter(org=org, name=name).first() # all else fails, create the objects from scratch if not group: group = ContactGroup.create(org, user, campaign_spec['group']['name']) if not campaign: campaign = Campaign.objects.create( name=Campaign.get_unique_name(name, org), org=org, group=group, created_by=user, modified_by=user) else: campaign.group = group campaign.save() # we want to nuke old single message flows for event in campaign.events.all(): if event.flow.flow_type == Flow.MESSAGE: event.flow.delete() # and all of the events, we'll recreate these campaign.events.all().delete() # fill our campaign with events for event_spec in campaign_spec['events']: relative_to = ContactField.get_or_create( org, key=event_spec['relative_to']['key'], label=event_spec['relative_to']['label']) # create our message flow for message events if event_spec['event_type'] == MESSAGE_EVENT: flow = Flow.create_single_message( org, user, event_spec['message']) else: flow = Flow.objects.filter( org=org, id=event_spec['flow']['id']).first() if flow: event = campaign.events.create( created_by=user, modified_by=user, offset=event_spec['offset'], unit=event_spec['unit'], event_type=event_spec['event_type'], delivery_hour=event_spec['delivery_hour'], message=event_spec['message'], flow=flow, relative_to=relative_to) event.update_flow_name() # update our scheduled events for this campaign EventFire.update_campaign_events(campaign)
def import_campaigns(cls, exported_json, org, user, same_site=False): """ Import campaigns from our export file """ from temba.orgs.models import EARLIEST_IMPORT_VERSION if Flow.is_before_version(exported_json.get("version", "0"), EARLIEST_IMPORT_VERSION): # pragma: needs cover raise ValueError(_("Unknown version (%s)" % exported_json.get("version", 0))) if "campaigns" in exported_json: for campaign_spec in exported_json["campaigns"]: name = campaign_spec["name"] campaign = None group = None # first check if we have the objects by id if same_site: group = ContactGroup.user_groups.filter(uuid=campaign_spec["group"]["uuid"], org=org).first() if group: # pragma: needs cover group.name = campaign_spec["group"]["name"] group.save() campaign = Campaign.objects.filter(org=org, uuid=campaign_spec["uuid"]).first() if campaign: # pragma: needs cover campaign.name = Campaign.get_unique_name(org, name, ignore=campaign) campaign.save() # fall back to lookups by name if not group: group = ContactGroup.get_user_group(org, campaign_spec["group"]["name"]) if not campaign: campaign = Campaign.objects.filter(org=org, name=name).first() # all else fails, create the objects from scratch if not group: group = ContactGroup.create_static(org, user, campaign_spec["group"]["name"]) if not campaign: campaign_name = Campaign.get_unique_name(org, name) campaign = Campaign.create(org, user, campaign_name, group) else: campaign.group = group campaign.save() # deactivate all of our events, we'll recreate these for event in campaign.events.all(): event.release() # fill our campaign with events for event_spec in campaign_spec["events"]: field_key = event_spec["relative_to"]["key"] if field_key == "created_on": relative_to = ContactField.system_fields.filter(org=org, key=field_key).first() else: relative_to = ContactField.get_or_create( org, user, key=field_key, label=event_spec["relative_to"]["label"], value_type="D" ) start_mode = event_spec.get("start_mode", CampaignEvent.MODE_INTERRUPT) # create our message flow for message events if event_spec["event_type"] == CampaignEvent.TYPE_MESSAGE: message = event_spec["message"] base_language = event_spec.get("base_language") if not isinstance(message, dict): try: message = json.loads(message) except ValueError: # if it's not a language dict, turn it into one message = dict(base=message) base_language = "base" event = CampaignEvent.create_message_event( org, user, campaign, relative_to, event_spec["offset"], event_spec["unit"], message, event_spec["delivery_hour"], base_language=base_language, start_mode=start_mode, ) event.update_flow_name() else: flow = Flow.objects.filter( org=org, is_active=True, is_system=False, uuid=event_spec["flow"]["uuid"] ).first() if flow: CampaignEvent.create_flow_event( org, user, campaign, relative_to, event_spec["offset"], event_spec["unit"], flow, event_spec["delivery_hour"], start_mode=start_mode, ) # update our scheduled events for this campaign EventFire.update_campaign_events(campaign)
def test_category_results(self): self.setup_color_gender_flow() # create a state field: # assign c1 and c2 to Kigali state = ContactField.get_or_create(self.org, 'state', label="State", value_type=STATE) district = ContactField.get_or_create(self.org, 'district', label="District", value_type=DISTRICT) self.c1.set_field('state', "Kigali City") self.c1.set_field('district', "Kigali") self.c2.set_field('state', "Kigali City") self.c2.set_field('district', "Kigali") self.run_color_gender_flow(self.c1, "red", "male", "16") self.run_color_gender_flow(self.c2, "blue", "female", "19") self.run_color_gender_flow(self.c3, "green", "male", "75") self.run_color_gender_flow(self.c4, "maroon", "female", "50") # create a group of the women ladies = self.create_group("Ladies", [self.c2, self.c4]) # get our rulesets color = RuleSet.objects.get(flow=self.flow, label="Color") gender = RuleSet.objects.get(flow=self.flow, label="Gender") age = RuleSet.objects.get(flow=self.flow, label="Age") # categories should be in the same order as our rules, should have correct counts result = Value.get_value_summary(ruleset=color)[0] self.assertEquals(3, len(result['categories'])) self.assertFalse(result['open_ended']) self.assertResult(result, 0, "Red", 2) self.assertResult(result, 1, "Blue", 1) self.assertResult(result, 2, "Green", 1) # check our age category as well result = Value.get_value_summary(ruleset=age)[0] self.assertEquals(3, len(result['categories'])) self.assertFalse(result['open_ended']) self.assertResult(result, 0, "Child", 1) self.assertResult(result, 1, "Adult", 2) self.assertResult(result, 2, "Senior", 1) # and our gender categories result = Value.get_value_summary(ruleset=gender)[0] self.assertEquals(2, len(result['categories'])) self.assertFalse(result['open_ended']) self.assertResult(result, 0, "Male", 2) self.assertResult(result, 1, "Female", 2) # now filter the results and only get responses by men result = Value.get_value_summary(ruleset=color, filters=[dict(ruleset=gender.pk, categories=["Male"])])[0] self.assertResult(result, 0, "Red", 1) self.assertResult(result, 1, "Blue", 0) self.assertResult(result, 2, "Green", 1) # what about men that are adults? result = Value.get_value_summary(ruleset=color, filters=[dict(ruleset=gender.pk, categories=["Male"]), dict(ruleset=age.pk, categories=["Adult"])])[0] self.assertResult(result, 0, "Red", 0) self.assertResult(result, 1, "Blue", 0) self.assertResult(result, 2, "Green", 0) # union of all genders result = Value.get_value_summary(ruleset=color, filters=[dict(ruleset=gender.pk, categories=["Male", "Female"]), dict(ruleset=age.pk, categories=["Adult"])])[0] self.assertResult(result, 0, "Red", 1) self.assertResult(result, 1, "Blue", 1) self.assertResult(result, 2, "Green", 0) # just women adults by group result = Value.get_value_summary(ruleset=color, filters=[dict(groups=[ladies.pk]), dict(ruleset=age.pk, categories="Adult")])[0] self.assertResult(result, 0, "Red", 1) self.assertResult(result, 1, "Blue", 1) self.assertResult(result, 2, "Green", 0) # remove one of the women from the group ladies.update_contacts([self.c2], False) # get a new summary result = Value.get_value_summary(ruleset=color, filters=[dict(groups=[ladies.pk]), dict(ruleset=age.pk, categories="Adult")])[0] self.assertResult(result, 0, "Red", 1) self.assertResult(result, 1, "Blue", 0) self.assertResult(result, 2, "Green", 0) # ok, back in she goes ladies.update_contacts([self.c2], True) # do another run for contact 1 run5 = self.run_color_gender_flow(self.c1, "blue", "male", "16") # totals should reflect the new value, not the old result = Value.get_value_summary(ruleset=color)[0] self.assertResult(result, 0, "Red", 1) self.assertResult(result, 1, "Blue", 2) self.assertResult(result, 2, "Green", 1) # what if we do a partial run? self.send_message(self.flow, "red", contact=self.c1, restart_participants=True) # should change our male/female breakdown since c1 now no longer has a gender result = Value.get_value_summary(ruleset=gender)[0] self.assertEquals(2, len(result['categories'])) self.assertResult(result, 0, "Male", 1) self.assertResult(result, 1, "Female", 2) # back to a full flow run5 = self.run_color_gender_flow(self.c1, "blue", "male", "16") # ok, now segment by gender result = Value.get_value_summary(ruleset=color, filters=[], segment=dict(ruleset=gender.pk, categories=["Male", "Female"])) male_result = result[0] self.assertResult(male_result, 0, "Red", 0) self.assertResult(male_result, 1, "Blue", 1) self.assertResult(male_result, 2, "Green", 1) female_result = result[1] self.assertResult(female_result, 0, "Red", 1) self.assertResult(female_result, 1, "Blue", 1) self.assertResult(female_result, 2, "Green", 0) # segment by gender again, but use the contact field to do so result = Value.get_value_summary(ruleset=color, filters=[], segment=dict(contact_field="Gender", values=["MALE", "Female"])) male_result = result[0] self.assertResult(male_result, 0, "Red", 0) self.assertResult(male_result, 1, "Blue", 1) self.assertResult(male_result, 2, "Green", 1) female_result = result[1] self.assertResult(female_result, 0, "Red", 1) self.assertResult(female_result, 1, "Blue", 1) self.assertResult(female_result, 2, "Green", 0) # add in a filter at the same time result = Value.get_value_summary(ruleset=color, filters=[dict(ruleset=color.pk, categories=["Blue"])], segment=dict(ruleset=gender.pk, categories=["Male", "Female"])) male_result = result[0] self.assertResult(male_result, 0, "Red", 0) self.assertResult(male_result, 1, "Blue", 1) self.assertResult(male_result, 2, "Green", 0) female_result = result[1] self.assertResult(female_result, 0, "Red", 0) self.assertResult(female_result, 1, "Blue", 1) self.assertResult(female_result, 2, "Green", 0) # ok, try segmenting by location instead result = Value.get_value_summary(ruleset=color, segment=dict(location="State")) eastern_result = result[0] self.assertEquals('171591', eastern_result['boundary']) self.assertEquals('Eastern Province', eastern_result['label']) self.assertResult(eastern_result, 0, "Red", 0) self.assertResult(eastern_result, 1, "Blue", 0) self.assertResult(eastern_result, 2, "Green", 0) kigali_result = result[1] self.assertEquals('1708283', kigali_result['boundary']) self.assertEquals('Kigali City', kigali_result['label']) self.assertResult(kigali_result, 0, "Red", 0) self.assertResult(kigali_result, 1, "Blue", 2) self.assertResult(kigali_result, 2, "Green", 0) # updating state location leads to updated data self.c2.set_field('state', "Eastern Province") result = Value.get_value_summary(ruleset=color, segment=dict(location="State")) eastern_result = result[0] self.assertEquals('171591', eastern_result['boundary']) self.assertEquals('Eastern Province', eastern_result['label']) self.assertResult(eastern_result, 0, "Red", 0) self.assertResult(eastern_result, 1, "Blue", 1) self.assertResult(eastern_result, 2, "Green", 0) kigali_result = result[1] self.assertEquals('1708283', kigali_result['boundary']) self.assertEquals('Kigali City', kigali_result['label']) self.assertResult(kigali_result, 0, "Red", 0) self.assertResult(kigali_result, 1, "Blue", 1) self.assertResult(kigali_result, 2, "Green", 0) # segment by district instead result = Value.get_value_summary(ruleset=color, segment=dict(parent="1708283", location="District")) # only on district in kigali self.assertEquals(1, len(result)) kigali_result = result[0] self.assertEquals('60485579', kigali_result['boundary']) self.assertEquals('Kigali', kigali_result['label']) self.assertResult(kigali_result, 0, "Red", 0) self.assertResult(kigali_result, 1, "Blue", 2) self.assertResult(kigali_result, 2, "Green", 0) # do a sanity check on our choropleth view self.login(self.admin) response = self.client.get(reverse('flows.ruleset_choropleth', args=[color.pk]) + "?_format=json&boundary=" + self.org.country.osm_id) # response should be valid json response = json.loads(response.content) # should have breaks self.assertTrue('breaks' in response) # should have two categories, Blue and Others self.assertEquals(2, len(response['categories'])) self.assertEquals("Blue", response['categories'][0]) self.assertEquals("Others", response['categories'][1]) # assert our kigali result kigali_result = response['scores']['1708283'] self.assertEquals(1, kigali_result['score']) self.assertEquals("Kigali City", kigali_result['name']) self.assertEquals("Blue", kigali_result['results'][0]['label']) self.assertEquals("Others", kigali_result['results'][1]['label']) self.assertEquals(1, kigali_result['results'][0]['count']) self.assertEquals(0, kigali_result['results'][1]['count']) self.assertEquals(100, kigali_result['results'][0]['percentage']) self.assertEquals(0, kigali_result['results'][1]['percentage']) with patch('temba.values.models.Value.get_value_summary') as mock: mock.return_value = [] response = self.client.get(reverse('flows.ruleset_choropleth', args=[color.pk]) + "?_format=json&boundary=" + self.org.country.osm_id) # response should be valid json response = json.loads(response.content) # should have two categories, Blue and Others self.assertEquals(2, len(response['categories'])) self.assertEquals("", response['categories'][0]) self.assertEquals("", response['categories'][1]) # all counts and percentage are 0 self.assertEquals(0, response['totals']['count']) self.assertEquals(0, response['totals']['results'][0]['count']) self.assertEquals(0, response['totals']['results'][0]['percentage']) self.assertEquals(0, response['totals']['results'][1]['count']) self.assertEquals(0, response['totals']['results'][1]['percentage']) # and empty string labels self.assertEquals("", response['totals']['results'][0]['label']) self.assertEquals("", response['totals']['results'][1]['label']) # also check our analytics view response = self.client.get(reverse('flows.ruleset_analytics')) # make sure we have only one flow in it flows = json.loads(response.context['flows']) self.assertEquals(1, len(flows)) self.assertEquals(3, len(flows[0]['rules']))
def test_field_results(self): c1 = self.create_contact("Contact1", '0788111111') c2 = self.create_contact("Contact2", '0788222222') c3 = self.create_contact("Contact3", '0788333333') self.create_contact("Contact4", '0788444444') # create a gender field that uses strings gender = ContactField.get_or_create(self.org, self.admin, 'gender', label="Gender", value_type=Value.TYPE_TEXT) c1.set_field(self.user, 'gender', "Male") c2.set_field(self.user, 'gender', "Female") c3.set_field(self.user, 'gender', "Female") result = Value.get_value_summary(contact_field=gender)[0] self.assertEquals(2, len(result['categories'])) self.assertEquals(3, result['set']) self.assertEquals( 2, result['unset'] ) # this is two as we have the default contact created by our unit tests self.assertFalse(result['open_ended']) self.assertResult(result, 0, "Female", 2) self.assertResult(result, 1, "Male", 1) # create an born field that uses decimals born = ContactField.get_or_create(self.org, self.admin, 'born', label="Born", value_type=Value.TYPE_DECIMAL) c1.set_field(self.user, 'born', 1977) c2.set_field(self.user, 'born', 1990) c3.set_field(self.user, 'born', 1977) result = Value.get_value_summary(contact_field=born)[0] self.assertEquals(2, len(result['categories'])) self.assertEquals(3, result['set']) self.assertEquals(2, result['unset']) self.assertFalse(result['open_ended']) self.assertResult(result, 0, "1977", 2) self.assertResult(result, 1, "1990", 1) # ok, state field! state = ContactField.get_or_create(self.org, self.admin, 'state', label="State", value_type=Value.TYPE_STATE) c1.set_field(self.user, 'state', "Kigali City") c2.set_field(self.user, 'state', "Kigali City") result = Value.get_value_summary(contact_field=state)[0] self.assertEquals(1, len(result['categories'])) self.assertEquals(2, result['set']) self.assertEquals(3, result['unset']) self.assertResult(result, 0, "1708283", 2) reg_date = ContactField.get_or_create(self.org, self.admin, 'reg_date', label="Registration Date", value_type=Value.TYPE_DATETIME) now = timezone.now() c1.set_field(self.user, 'reg_date', now.replace(hour=9)) c2.set_field(self.user, 'reg_date', now.replace(hour=4)) c3.set_field(self.user, 'reg_date', now - timedelta(days=1)) result = Value.get_value_summary(contact_field=reg_date)[0] self.assertEquals(2, len(result['categories'])) self.assertEquals(3, result['set']) self.assertEquals(2, result['unset']) self.assertResult( result, 0, now.replace(hour=0, minute=0, second=0, microsecond=0), 2) self.assertResult(result, 1, (now - timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0), 1) # make sure categories returned are sorted by count, not name c2.set_field(self.user, 'gender', "Male") result = Value.get_value_summary(contact_field=gender)[0] self.assertEquals(2, len(result['categories'])) self.assertEquals(3, result['set']) self.assertEquals( 2, result['unset'] ) # this is two as we have the default contact created by our unit tests self.assertFalse(result['open_ended']) self.assertResult(result, 0, "Male", 2) self.assertResult(result, 1, "Female", 1) # check the modified date is tracked for fields original_value = Value.objects.get(contact=c1, contact_field=gender) c1.set_field(self.user, 'gender', 'unknown') new_value = Value.objects.get(contact=c1, contact_field=gender) self.assertTrue(new_value.modified_on > original_value.modified_on) self.assertNotEqual(new_value.string_value, original_value.string_value)
def test_scheduling(self): campaign = Campaign.create(self.org, self.admin, "Planting Reminders", self.farmers) self.assertEquals("Planting Reminders", unicode(campaign)) # create a reminder for our first planting event planting_reminder = CampaignEvent.create_flow_event(self.org, self.admin, campaign, relative_to=self.planting_date, offset=0, unit='D', flow=self.reminder_flow, delivery_hour=17) self.assertEquals("Planting Date == 0 -> Color Flow", unicode(planting_reminder)) # schedule our reminders EventFire.update_campaign_events(campaign) # we should haven't any event fires created, since neither of our farmers have a planting date self.assertEquals(0, EventFire.objects.all().count()) # ok, set a planting date on one of our contacts self.farmer1.set_field('planting_date', "05-10-2020 12:30:10") # update our campaign events EventFire.update_campaign_events(campaign) # should have one event now fire = EventFire.objects.get() self.assertEquals(5, fire.scheduled.day) self.assertEquals(10, fire.scheduled.month) self.assertEquals(2020, fire.scheduled.year) # account for timezone difference, our org is in UTC+2 self.assertEquals(17 - 2, fire.scheduled.hour) self.assertEquals(self.farmer1, fire.contact) self.assertEquals(planting_reminder, fire.event) self.assertIsNone(fire.fired) # change the date of our date self.farmer1.set_field('planting_date', "06-10-2020 12:30:10") EventFire.update_campaign_events_for_contact(campaign, self.farmer1) fire = EventFire.objects.get() self.assertEquals(6, fire.scheduled.day) self.assertEquals(10, fire.scheduled.month) self.assertEquals(2020, fire.scheduled.year) self.assertEquals(self.farmer1, fire.contact) self.assertEquals(planting_reminder, fire.event) # set it to something invalid self.farmer1.set_field('planting_date', "what?") EventFire.update_campaign_events_for_contact(campaign, self.farmer1) self.assertFalse(EventFire.objects.all()) # now something valid again self.farmer1.set_field('planting_date', "07-10-2020 12:30:10") EventFire.update_campaign_events_for_contact(campaign, self.farmer1) fire = EventFire.objects.get() self.assertEquals(7, fire.scheduled.day) self.assertEquals(10, fire.scheduled.month) self.assertEquals(2020, fire.scheduled.year) self.assertEquals(self.farmer1, fire.contact) self.assertEquals(planting_reminder, fire.event) # create another reminder planting_reminder2 = CampaignEvent.create_flow_event(self.org, self.admin, campaign, relative_to=self.planting_date, offset=1, unit='D', flow=self.reminder2_flow) self.assertEquals(1, planting_reminder2.abs_offset()) # update the campaign EventFire.update_campaign_events(campaign) # should have two events now, ordered by date events = EventFire.objects.all() self.assertEquals(planting_reminder, events[0].event) self.assertEquals(7, events[0].scheduled.day) self.assertEquals(planting_reminder2, events[1].event) self.assertEquals(8, events[1].scheduled.day) # mark one of the events as inactive planting_reminder2.is_active = False planting_reminder2.save() # update the campaign EventFire.update_campaign_events(campaign) # back to only one event event = EventFire.objects.get() self.assertEquals(planting_reminder, event.event) self.assertEquals(7, event.scheduled.day) # update our date self.farmer1.set_field('planting_date', '09-10-2020 12:30') # should have updated event = EventFire.objects.get() self.assertEquals(planting_reminder, event.event) self.assertEquals(9, event.scheduled.day) # let's remove our contact field ContactField.hide_field(self.org, 'planting_date') # shouldn't have anything scheduled self.assertFalse(EventFire.objects.all()) # add it back in ContactField.get_or_create(self.org, 'planting_date', "planting Date") # should be back! event = EventFire.objects.get() self.assertEquals(planting_reminder, event.event) self.assertEquals(9, event.scheduled.day) # change our fire date to sometimein the past so it gets triggered event.scheduled = timezone.now() - timedelta(hours=1) event.save() # schedule our events to fire check_campaigns_task() # should have one flow run now run = FlowRun.objects.get() self.assertEquals(event.contact, run.contact)
def setUp(self): self.clear_cache() self.user = self.create_user("tito") self.admin = self.create_user("Administrator") self.org = Org.objects.create(name="Nyaruka Ltd.", timezone="Africa/Kigali", created_by=self.user, modified_by=self.user) self.org.initialize() self.org.administrators.add(self.admin) self.admin.set_org(self.org) self.org.administrators.add(self.user) self.user.set_org(self.org) self.tel_mtn = Channel.create(self.org, self.user, 'RW', 'A', name="MTN", address="+250780000000", secret="12345", gcm_id="123") self.tel_tigo = Channel.create(self.org, self.user, 'RW', 'A', name="Tigo", address="+250720000000", secret="23456", gcm_id="234") self.tel_bulk = Channel.create(self.org, self.user, 'RW', 'NX', name="Nexmo", parent=self.tel_tigo) self.twitter = Channel.create(self.org, self.user, None, 'TT', name="Twitter", address="billy_bob") # for generating tuples of scheme, path and channel def generate_tel_mtn(num): return TEL_SCHEME, "+25078%07d" % (num + 1), self.tel_mtn def generate_tel_tigo(num): return TEL_SCHEME, "+25072%07d" % (num + 1), self.tel_tigo def generate_twitter(num): return TWITTER_SCHEME, "tweep_%d" % (num + 1), self.twitter self.urn_generators = (generate_tel_mtn, generate_tel_tigo, generate_twitter) self.field_nick = ContactField.get_or_create( self.org, self.admin, 'nick', 'Nickname', show_in_table=True, value_type=Value.TYPE_TEXT) self.field_age = ContactField.get_or_create( self.org, self.admin, 'age', 'Age', show_in_table=True, value_type=Value.TYPE_DECIMAL)
def test_category_results(self): self.setup_color_gender_flow() # create a state field: # assign c1 and c2 to Kigali state = ContactField.get_or_create(self.org, 'state', label="State", value_type=STATE) district = ContactField.get_or_create(self.org, 'district', label="District", value_type=DISTRICT) self.c1.set_field('state', "Kigali City") self.c1.set_field('district', "Kigali") self.c2.set_field('state', "Kigali City") self.c2.set_field('district', "Kigali") self.run_color_gender_flow(self.c1, "red", "male", "16") self.run_color_gender_flow(self.c2, "blue", "female", "19") self.run_color_gender_flow(self.c3, "green", "male", "75") self.run_color_gender_flow(self.c4, "maroon", "female", "50") # create a group of the women ladies = self.create_group("Ladies", [self.c2, self.c4]) # get our rulesets color = RuleSet.objects.get(flow=self.flow, label="Color") gender = RuleSet.objects.get(flow=self.flow, label="Gender") age = RuleSet.objects.get(flow=self.flow, label="Age") # categories should be in the same order as our rules, should have correct counts result = Value.get_value_summary(ruleset=color)[0] self.assertEquals(3, len(result['categories'])) self.assertFalse(result['open_ended']) self.assertResult(result, 0, "Red", 2) self.assertResult(result, 1, "Blue", 1) self.assertResult(result, 2, "Green", 1) # check our age category as well result = Value.get_value_summary(ruleset=age)[0] self.assertEquals(3, len(result['categories'])) self.assertFalse(result['open_ended']) self.assertResult(result, 0, "Child", 1) self.assertResult(result, 1, "Adult", 2) self.assertResult(result, 2, "Senior", 1) # and our gender categories result = Value.get_value_summary(ruleset=gender)[0] self.assertEquals(2, len(result['categories'])) self.assertFalse(result['open_ended']) self.assertResult(result, 0, "Male", 2) self.assertResult(result, 1, "Female", 2) # now filter the results and only get responses by men result = Value.get_value_summary(ruleset=color, filters=[dict(ruleset=gender.pk, categories=["Male"])])[0] self.assertResult(result, 0, "Red", 1) self.assertResult(result, 1, "Blue", 0) self.assertResult(result, 2, "Green", 1) # what about men that are adults? result = Value.get_value_summary(ruleset=color, filters=[dict(ruleset=gender.pk, categories=["Male"]), dict(ruleset=age.pk, categories=["Adult"])])[0] self.assertResult(result, 0, "Red", 0) self.assertResult(result, 1, "Blue", 0) self.assertResult(result, 2, "Green", 0) # union of all genders result = Value.get_value_summary(ruleset=color, filters=[dict(ruleset=gender.pk, categories=["Male", "Female"]), dict(ruleset=age.pk, categories=["Adult"])])[0] self.assertResult(result, 0, "Red", 1) self.assertResult(result, 1, "Blue", 1) self.assertResult(result, 2, "Green", 0) # just women adults by group result = Value.get_value_summary(ruleset=color, filters=[dict(groups=[ladies.pk]), dict(ruleset=age.pk, categories="Adult")])[0] self.assertResult(result, 0, "Red", 1) self.assertResult(result, 1, "Blue", 1) self.assertResult(result, 2, "Green", 0) # remove one of the women from the group ladies.update_contacts([self.c2], False) # get a new summary result = Value.get_value_summary(ruleset=color, filters=[dict(groups=[ladies.pk]), dict(ruleset=age.pk, categories="Adult")])[0] self.assertResult(result, 0, "Red", 1) self.assertResult(result, 1, "Blue", 0) self.assertResult(result, 2, "Green", 0) # ok, back in she goes ladies.update_contacts([self.c2], True) # do another run for contact 1 run5 = self.run_color_gender_flow(self.c1, "blue", "male", "16") # totals should reflect the new value, not the old result = Value.get_value_summary(ruleset=color)[0] self.assertResult(result, 0, "Red", 1) self.assertResult(result, 1, "Blue", 2) self.assertResult(result, 2, "Green", 1) # what if we do a partial run? self.send_message(self.flow, "red", contact=self.c1, restart_participants=True) # should change our male/female breakdown since c1 now no longer has a gender result = Value.get_value_summary(ruleset=gender)[0] self.assertEquals(2, len(result['categories'])) self.assertResult(result, 0, "Male", 1) self.assertResult(result, 1, "Female", 2) # back to a full flow run5 = self.run_color_gender_flow(self.c1, "blue", "male", "16") # ok, now segment by gender result = Value.get_value_summary(ruleset=color, filters=[], segment=dict(ruleset=gender.pk, categories=["Male", "Female"])) male_result = result[0] self.assertResult(male_result, 0, "Red", 0) self.assertResult(male_result, 1, "Blue", 1) self.assertResult(male_result, 2, "Green", 1) female_result = result[1] self.assertResult(female_result, 0, "Red", 1) self.assertResult(female_result, 1, "Blue", 1) self.assertResult(female_result, 2, "Green", 0) # add in a filter at the same time result = Value.get_value_summary(ruleset=color, filters=[dict(ruleset=color.pk, categories=["Blue"])], segment=dict(ruleset=gender.pk, categories=["Male", "Female"])) male_result = result[0] self.assertResult(male_result, 0, "Red", 0) self.assertResult(male_result, 1, "Blue", 1) self.assertResult(male_result, 2, "Green", 0) female_result = result[1] self.assertResult(female_result, 0, "Red", 0) self.assertResult(female_result, 1, "Blue", 1) self.assertResult(female_result, 2, "Green", 0) # ok, try segmenting by location instead result = Value.get_value_summary(ruleset=color, segment=dict(location="State")) eastern_result = result[0] self.assertEquals('171591', eastern_result['boundary']) self.assertEquals('Eastern Province', eastern_result['label']) self.assertResult(eastern_result, 0, "Red", 0) self.assertResult(eastern_result, 1, "Blue", 0) self.assertResult(eastern_result, 2, "Green", 0) kigali_result = result[1] self.assertEquals('1708283', kigali_result['boundary']) self.assertEquals('Kigali City', kigali_result['label']) self.assertResult(kigali_result, 0, "Red", 0) self.assertResult(kigali_result, 1, "Blue", 2) self.assertResult(kigali_result, 2, "Green", 0) # updating state location leads to updated data self.c2.set_field('state', "Eastern Province") result = Value.get_value_summary(ruleset=color, segment=dict(location="State")) eastern_result = result[0] self.assertEquals('171591', eastern_result['boundary']) self.assertEquals('Eastern Province', eastern_result['label']) self.assertResult(eastern_result, 0, "Red", 0) self.assertResult(eastern_result, 1, "Blue", 1) self.assertResult(eastern_result, 2, "Green", 0) kigali_result = result[1] self.assertEquals('1708283', kigali_result['boundary']) self.assertEquals('Kigali City', kigali_result['label']) self.assertResult(kigali_result, 0, "Red", 0) self.assertResult(kigali_result, 1, "Blue", 1) self.assertResult(kigali_result, 2, "Green", 0) # segment by district instead result = Value.get_value_summary(ruleset=color, segment=dict(parent="1708283", location="District")) # only on district in kigali self.assertEquals(1, len(result)) kigali_result = result[0] self.assertEquals('60485579', kigali_result['boundary']) self.assertEquals('Kigali', kigali_result['label']) self.assertResult(kigali_result, 0, "Red", 0) self.assertResult(kigali_result, 1, "Blue", 2) self.assertResult(kigali_result, 2, "Green", 0)
def test_scheduling(self): campaign = Campaign.objects.create(name="Planting Reminders", group=self.farmers, org=self.org, created_by=self.admin, modified_by=self.admin) self.assertEquals("Planting Reminders", unicode(campaign)) # create a reminder for our first planting event planting_reminder = CampaignEvent.objects.create(campaign=campaign, relative_to=self.planting_date, offset=0, flow=self.reminder_flow, delivery_hour=17, created_by=self.admin, modified_by=self.admin) self.assertEquals("Planting Date == 0 -> Color Flow", unicode(planting_reminder)) # schedule our reminders EventFire.update_campaign_events(campaign) # we should haven't any event fires created, since neither of our farmers have a planting date self.assertEquals(0, EventFire.objects.all().count()) # ok, set a planting date on one of our contacts self.farmer1.set_field('planting_date', "05-10-2020 12:30:10") # update our campaign events EventFire.update_campaign_events(campaign) # should have one event now fire = EventFire.objects.get() self.assertEquals(5, fire.scheduled.day) self.assertEquals(10, fire.scheduled.month) self.assertEquals(2020, fire.scheduled.year) # account for timezone difference, our org is in UTC+2 self.assertEquals(17 - 2, fire.scheduled.hour) self.assertEquals(self.farmer1, fire.contact) self.assertEquals(planting_reminder, fire.event) self.assertIsNone(fire.fired) # change the date of our date self.farmer1.set_field('planting_date', "06-10-2020 12:30:10") EventFire.update_campaign_events_for_contact(campaign, self.farmer1) fire = EventFire.objects.get() self.assertEquals(6, fire.scheduled.day) self.assertEquals(10, fire.scheduled.month) self.assertEquals(2020, fire.scheduled.year) self.assertEquals(self.farmer1, fire.contact) self.assertEquals(planting_reminder, fire.event) # set it to something invalid self.farmer1.set_field('planting_date', "what?") EventFire.update_campaign_events_for_contact(campaign, self.farmer1) self.assertFalse(EventFire.objects.all()) # now something valid again self.farmer1.set_field('planting_date', "07-10-2020 12:30:10") EventFire.update_campaign_events_for_contact(campaign, self.farmer1) fire = EventFire.objects.get() self.assertEquals(7, fire.scheduled.day) self.assertEquals(10, fire.scheduled.month) self.assertEquals(2020, fire.scheduled.year) self.assertEquals(self.farmer1, fire.contact) self.assertEquals(planting_reminder, fire.event) # create another reminder planting_reminder2 = CampaignEvent.objects.create(campaign=campaign, relative_to=self.planting_date, offset=1, flow=self.reminder2_flow, created_by=self.admin, modified_by=self.admin) self.assertEquals(1, planting_reminder2.abs_offset()) # update the campaign EventFire.update_campaign_events(campaign) # should have two events now, ordered by date events = EventFire.objects.all() self.assertEquals(planting_reminder, events[0].event) self.assertEquals(7, events[0].scheduled.day) self.assertEquals(planting_reminder2, events[1].event) self.assertEquals(8, events[1].scheduled.day) # mark one of the events as inactive planting_reminder2.is_active = False planting_reminder2.save() # update the campaign EventFire.update_campaign_events(campaign) # back to only one event event = EventFire.objects.get() self.assertEquals(planting_reminder, event.event) self.assertEquals(7, event.scheduled.day) # update our date self.farmer1.set_field('planting_date', '09-10-2020 12:30') # should have updated event = EventFire.objects.get() self.assertEquals(planting_reminder, event.event) self.assertEquals(9, event.scheduled.day) # let's remove our contact field ContactField.hide_field(self.org, 'planting_date') # shouldn't have anything scheduled self.assertFalse(EventFire.objects.all()) # add it back in ContactField.get_or_create(self.org, 'planting_date', "planting Date") # should be back! event = EventFire.objects.get() self.assertEquals(planting_reminder, event.event) self.assertEquals(9, event.scheduled.day) # try firing the event event.fire() # should have one flow run now run = FlowRun.objects.get() self.assertEquals(event.contact, run.contact)
def test_field_results(self): c1 = self.create_contact("Contact1", '0788111111') c2 = self.create_contact("Contact2", '0788222222') c3 = self.create_contact("Contact3", '0788333333') self.create_contact("Contact4", '0788444444') # create a gender field that uses strings gender = ContactField.get_or_create(self.org, self.admin, 'gender', label="Gender", value_type=Value.TYPE_TEXT) c1.set_field(self.user, 'gender', "Male") c2.set_field(self.user, 'gender', "Female") c3.set_field(self.user, 'gender', "Female") result = Value.get_value_summary(contact_field=gender)[0] self.assertEquals(2, len(result['categories'])) self.assertEquals(3, result['set']) self.assertEquals(2, result['unset']) # this is two as we have the default contact created by our unit tests self.assertFalse(result['open_ended']) self.assertResult(result, 0, "Female", 2) self.assertResult(result, 1, "Male", 1) # create an born field that uses decimals born = ContactField.get_or_create(self.org, self.admin, 'born', label="Born", value_type=Value.TYPE_DECIMAL) c1.set_field(self.user, 'born', 1977) c2.set_field(self.user, 'born', 1990) c3.set_field(self.user, 'born', 1977) result = Value.get_value_summary(contact_field=born)[0] self.assertEquals(2, len(result['categories'])) self.assertEquals(3, result['set']) self.assertEquals(2, result['unset']) self.assertFalse(result['open_ended']) self.assertResult(result, 0, "1977", 2) self.assertResult(result, 1, "1990", 1) # ok, state field! state = ContactField.get_or_create(self.org, self.admin, 'state', label="State", value_type=Value.TYPE_STATE) c1.set_field(self.user, 'state', "Kigali City") c2.set_field(self.user, 'state', "Kigali City") result = Value.get_value_summary(contact_field=state)[0] self.assertEquals(1, len(result['categories'])) self.assertEquals(2, result['set']) self.assertEquals(3, result['unset']) self.assertResult(result, 0, "1708283", 2) reg_date = ContactField.get_or_create(self.org, self.admin, 'reg_date', label="Registration Date", value_type=Value.TYPE_DATETIME) now = timezone.now() c1.set_field(self.user, 'reg_date', now.replace(hour=9)) c2.set_field(self.user, 'reg_date', now.replace(hour=4)) c3.set_field(self.user, 'reg_date', now - timedelta(days=1)) result = Value.get_value_summary(contact_field=reg_date)[0] self.assertEquals(2, len(result['categories'])) self.assertEquals(3, result['set']) self.assertEquals(2, result['unset']) self.assertResult(result, 0, now.replace(hour=0, minute=0, second=0, microsecond=0), 2) self.assertResult(result, 1, (now - timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0), 1) # make sure categories returned are sorted by count, not name c2.set_field(self.user, 'gender', "Male") result = Value.get_value_summary(contact_field=gender)[0] self.assertEquals(2, len(result['categories'])) self.assertEquals(3, result['set']) self.assertEquals(2, result['unset']) # this is two as we have the default contact created by our unit tests self.assertFalse(result['open_ended']) self.assertResult(result, 0, "Male", 2) self.assertResult(result, 1, "Female", 1) # check the modified date is tracked for fields original_value = Value.objects.get(contact=c1, contact_field=gender) c1.set_field(self.user, 'gender', 'unknown') new_value = Value.objects.get(contact=c1, contact_field=gender) self.assertTrue(new_value.modified_on > original_value.modified_on) self.assertNotEqual(new_value.string_value, original_value.string_value)