def start_call(self, call, to, from_, status_callback): channel = call.channel Contact.get_or_create(channel.org, channel.created_by, urns=[(TEL_SCHEME, to)]) # Verboice differs from Twilio in that they expect the first block of twiml up front payload = unicode(Flow.handle_call(call, {})) # our config should have our http basic auth parameters and verboice channel config = channel.config_json() # now we can post that to verboice url = "%s?%s" % (self.endpoint, urlencode(dict(channel=config['channel'], address=to))) response = requests.post(url, data=payload, auth=(config['username'], config['password'])).json() # store the verboice call id in our IVRCall call.external_id = response['call_id'] call.status = IN_PROGRESS call.save()
def start_call(self, call, to, from_, status_callback): if not settings.SEND_CALLS: raise ValueError("SEND_CALLS set to False, skipping call start") channel = call.channel Contact.get_or_create(channel.org, URN.from_tel(to), channel) # Verboice differs from Twilio in that they expect the first block of twiml up front payload = str(Flow.handle_call(call)) # now we can post that to verboice url = "%s?%s" % (self.endpoint, urlencode(dict(channel=self.verboice_channel, address=to))) response = requests.post(url, data=payload, auth=self.auth).json() if "call_id" not in response: call.status = IVRCall.FAILED call.save() raise IVRException(_("Verboice connection failed.")) # store the verboice call id in our IVRCall call.external_id = response["call_id"] # the call was successfully sent to the IVR provider call.status = IVRCall.WIRED call.save()
def start_call(self, call, to, from_, status_callback): if not settings.SEND_CALLS: raise IVRException("SEND_CALLS set to False, skipping call start") channel = call.channel Contact.get_or_create(channel.org, channel.created_by, urns=[URN.from_tel(to)]) # Verboice differs from Twilio in that they expect the first block of twiml up front payload = six.text_type(Flow.handle_call(call)) # now we can post that to verboice url = "%s?%s" % (self.endpoint, urlencode( dict(channel=self.verboice_channel, address=to))) response = requests.post(url, data=payload, auth=self.auth).json() if 'call_id' not in response: raise IVRException(_('Verboice connection failed.')) # store the verboice call id in our IVRCall call.external_id = response['call_id'] call.status = IVRCall.IN_PROGRESS call.save()
def save(self, obj): urns = [] for field_key, value in self.form.cleaned_data.iteritems(): if field_key.startswith('__urn__') and value: scheme = field_key[7:] urns.append((scheme, value)) Contact.get_or_create(obj.org, self.request.user, obj.name, urns)
def save(self, obj): urns = [] for field_key, value in self.form.cleaned_data.iteritems(): if field_key.startswith('__urn__') and value: scheme = field_key[7:] urns.append((scheme, value)) Contact.get_or_create(obj.org, self.request.user, obj.name, urns)
def create_contact(self, name=None, number=None, twitter=None, urn=None, is_test=False, **kwargs): """ Create a contact in the master test org """ urns = [] if number: urns.append((TEL_SCHEME, number)) if twitter: urns.append((TWITTER_SCHEME, twitter)) if urn: urns.append(urn) if not name and not urns: # pragma: no cover raise ValueError("Need a name or URN to create a contact") kwargs['name'] = name kwargs['urns'] = urns kwargs['is_test'] = is_test if 'org' not in kwargs: kwargs['org'] = self.org if 'user' not in kwargs: kwargs['user'] = self.user return Contact.get_or_create(**kwargs)
def create_contact(self, name=None, number=None, twitter=None, urn=None, is_test=False, **kwargs): """ Create a contact in the master test org """ urns = [] if number: urns.append(URN.from_tel(number)) if twitter: urns.append(URN.from_twitter(twitter)) if urn: urns.append(urn) if not name and not urns: # pragma: no cover raise ValueError("Need a name or URN to create a contact") kwargs['name'] = name kwargs['urns'] = urns kwargs['is_test'] = is_test if 'org' not in kwargs: kwargs['org'] = self.org if 'user' not in kwargs: kwargs['user'] = self.user return Contact.get_or_create(**kwargs)
def restore_object(self, attrs, instance=None): """ Actually start our flows for each contact """ if instance: # pragma: no cover raise ValidationError("Invalid operation") flow = attrs['flow'] groups = attrs.get('groups', []) contacts = attrs.get('contacts', []) extra = attrs.get('extra', None) # include contacts created/matched via deprecated phone field phone_urns = attrs.get('phone', []) if phone_urns: channel = self.org.get_send_channel(TEL_SCHEME) for urn in phone_urns: # treat each URN as separate contact contact = Contact.get_or_create(self.user, channel.org, urns=[urn], channel=channel) contacts.append(contact) if contacts or groups: return flow.start(groups, contacts, restart_participants=True, extra=extra) else: return []
def save(self): """ Create a new broadcast to send out """ if 'urn' in self.validated_data and self.validated_data['urn']: urns = self.validated_data.get('urn') else: urns = self.validated_data.get('phone', []) channel = self.validated_data.get('channel') contacts = list() for urn in urns: # treat each urn as a separate contact contacts.append( Contact.get_or_create(channel.org, self.user, urns=[urn])) # add any contacts specified by uuids uuid_contacts = self.validated_data.get('contact', []) for contact in uuid_contacts: contacts.append(contact) # create the broadcast broadcast = Broadcast.create(self.org, self.user, self.validated_data['text'], recipients=contacts, channel=channel) # send it broadcast.send(expressions_context={}) return broadcast
def save(self): """ Create a new broadcast to send out """ if "urn" in self.validated_data and self.validated_data["urn"]: urns = self.validated_data.get("urn") else: urns = self.validated_data.get("phone", []) channel = self.validated_data.get("channel") contacts = list() for urn in urns: # treat each urn as a separate contact contact, urn_obj = Contact.get_or_create(channel.org, urn, user=self.user) contacts.append(contact) # add any contacts specified by uuids uuid_contacts = self.validated_data.get("contact", []) for contact in uuid_contacts: contacts.append(contact) # create the broadcast broadcast = Broadcast.create( self.org, self.user, self.validated_data["text"], contacts=contacts, channel=channel ) # send it broadcast.send(expressions_context={}) return broadcast
def restore_object(self, attrs, instance=None): """ Create a new broadcast to send out """ if instance: # pragma: no cover raise ValidationError("Invalid operation") user = self.user org = self.org if 'urn' in attrs and attrs['urn']: urns = attrs.get('urn', []) else: urns = attrs.get('phone', []) # create the broadcast broadcast = Broadcast.create(user, attrs['text'], org=org) channel = attrs['channel'] contacts = list() for urn in urns: # treat each urn as a separate contact contacts.append(Contact.get_or_create(user, channel.org, urns=[urn], channel=channel)) # add any contacts specified by uuids uuid_contacts = attrs.get('contact', []) for contact in uuid_contacts: contacts.append(contact) broadcast.set_recipients(*contacts) # send it broadcast.send() return broadcast
def validate_urns(self, value): urn_contacts = [] for urn in value: contact = Contact.get_or_create(self.context['org'], self.context['user'], urns=[urn]) urn_contacts.append(contact) return urn_contacts
def restore_object(self, attrs, instance=None): """ Create a new broadcast to send out """ if instance: # pragma: no cover raise ValidationError("Invalid operation") user = self.user org = self.org if 'urn' in attrs and attrs['urn']: urns = attrs.get('urn', []) else: urns = attrs.get('phone', []) channel = attrs['channel'] contacts = list() for urn in urns: # treat each urn as a separate contact contacts.append(Contact.get_or_create(user, channel.org, urns=[urn], channel=channel)) # add any contacts specified by uuids uuid_contacts = attrs.get('contact', []) for contact in uuid_contacts: contacts.append(contact) # create the broadcast broadcast = Broadcast.create(org, user, attrs['text'], recipients=contacts) # send it broadcast.send() return broadcast
def start_call(self, call, to, from_, status_callback): channel = call.channel Contact.get_or_create(channel.org, channel.created_by, urns=[(TEL_SCHEME, to)]) # Verboice differs from Twilio in that they expect the first block of twiml up front payload = unicode(Flow.handle_call(call, {})) # now we can post that to verboice url = "%s?%s" % (self.endpoint, urlencode(dict(channel=self.verboice_channel, address=to))) response = requests.post(url, data=payload, auth=self.auth).json() # store the verboice call id in our IVRCall call.external_id = response['call_id'] call.status = IN_PROGRESS call.save()
def save(self): urns = self.validated_data.get("urns", []) contacts = self.validated_data.get("contacts", []) groups = self.validated_data.get("groups", []) restart_participants = self.validated_data.get("restart_participants", True) extra = self.validated_data.get("extra") params = self.validated_data.get("params") if params: extra = params # convert URNs to contacts for urn in urns: contact, urn_obj = Contact.get_or_create(self.context["org"], urn, user=self.context["user"]) contacts.append(contact) # ok, let's go create our flow start, the actual starting will happen in our view return FlowStart.create( self.validated_data["flow"], self.context["user"], restart_participants=restart_participants, contacts=contacts, groups=groups, extra=extra, )
def save(self): """ Create a new broadcast to send out """ contact_urns = [] for urn in self.validated_data.get("urns", []): # create contacts for URNs if necessary __, contact_urn = Contact.get_or_create(self.context["org"], urn, user=self.context["user"]) contact_urns.append(contact_urn) text, base_language = self.validated_data["text"] if not self.validated_data["new_expressions"]: text = migrate_translations(text) # create the broadcast broadcast = Broadcast.create( self.context["org"], self.context["user"], text=text, base_language=base_language, groups=self.validated_data.get("groups", []), contacts=self.validated_data.get("contacts", []), urns=contact_urns, channel=self.validated_data.get("channel"), template_state=Broadcast.TEMPLATE_STATE_UNEVALUATED, ) # send it on_transaction_commit(lambda: broadcast.send()) return broadcast
def restore_object(self, attrs, instance=None): """ Actually start our flows for each contact """ if instance: # pragma: no cover raise ValidationError("Invalid operation") flow = attrs['flow'] groups = attrs.get('groups', []) contacts = attrs.get('contacts', []) extra = attrs.get('extra', None) # include contacts created/matched via deprecated phone field phone_urns = attrs.get('phone', []) if phone_urns: channel = self.org.get_send_channel(TEL_SCHEME) for urn in phone_urns: # treat each URN as separate contact contact = Contact.get_or_create(self.user, channel.org, urns=[urn], channel=channel) contacts.append(contact) if contacts or groups: return flow.start(groups, contacts, restart_participants=True, extra=extra) else: return []
def save(self): """ Create a new broadcast to send out """ if "urn" in self.validated_data and self.validated_data["urn"]: urns = self.validated_data.get("urn") else: urns = self.validated_data.get("phone", []) channel = self.validated_data.get("channel") contacts = list() for urn in urns: # treat each urn as a separate contact contact, urn_obj = Contact.get_or_create(channel.org, urn, user=self.user) contacts.append(contact) # add any contacts specified by uuids uuid_contacts = self.validated_data.get("contact", []) for contact in uuid_contacts: contacts.append(contact) # create the broadcast broadcast = Broadcast.create(self.org, self.user, self.validated_data["text"], contacts=contacts, channel=channel) # send it broadcast.send(expressions_context={}) return broadcast
def save(self): """ Create a new broadcast to send out """ recipients = self.validated_data.get( 'contacts', []) + self.validated_data.get('groups', []) for urn in self.validated_data.get('urns', []): # create contacts for URNs if necessary contact = Contact.get_or_create(self.context['org'], self.context['user'], urns=[urn]) contact_urn = contact.urn_objects[urn] recipients.append(contact_urn) text, base_language = self.validated_data['text'] # create the broadcast broadcast = Broadcast.create( self.context['org'], self.context['user'], text=text, base_language=base_language, recipients=recipients, channel=self.validated_data.get('channel')) # send in task on_transaction_commit(lambda: send_broadcast_task.delay(broadcast.id)) return broadcast
def save(self): """ Create a new broadcast to send out """ contact_urns = [] for urn in self.validated_data.get("urns", []): # create contacts for URNs if necessary __, contact_urn = Contact.get_or_create(self.context["org"], urn, user=self.context["user"]) contact_urns.append(contact_urn) text, base_language = self.validated_data["text"] # create the broadcast broadcast = Broadcast.create( self.context["org"], self.context["user"], text=text, base_language=base_language, groups=self.validated_data.get("groups", []), contacts=self.validated_data.get("contacts", []), urns=contact_urns, channel=self.validated_data.get("channel"), ) # send in task on_transaction_commit(lambda: send_broadcast_task.delay(broadcast.id)) return broadcast
def save(self): """ Create a new broadcast to send out """ contact_urns = [] for urn in self.validated_data.get("urns", []): # create contacts for URNs if necessary __, contact_urn = Contact.get_or_create(self.context["org"], urn, user=self.context["user"]) contact_urns.append(contact_urn) text, base_language = self.validated_data["text"] # create the broadcast broadcast = Broadcast.create( self.context["org"], self.context["user"], text=text, base_language=base_language, groups=self.validated_data.get("groups", []), contacts=self.validated_data.get("contacts", []), urns=contact_urns, channel=self.validated_data.get("channel"), ) # send in task on_transaction_commit(lambda: send_broadcast_task.delay(broadcast.id)) return broadcast
def restore_object(self, attrs, instance=None): """ Actually start our flows for each contact """ if instance: # pragma: no cover raise ValidationError("Invalid operation") flow = attrs['flow'] channel = self.org.get_send_channel(TEL_SCHEME) if 'urn' in attrs and attrs['urn']: urns = attrs.get('urn', []) else: urns = attrs.get('phone', []) contacts = [] for urn in urns: # treat each URN as separate contact contact = Contact.get_or_create(self.user, channel.org, urns=[urn], channel=channel) contacts.append(contact) # also add in any contacts specified by uuid uuid_contacts = attrs.get('contact', []) contacts += uuid_contacts if contacts: runs = flow.start([], contacts, restart_participants=True, extra=attrs.get('extra', None)) else: runs = [] return runs
def get_or_create_contact(self, urn): if ':' in urn: parsed = ContactURN.parse_urn(urn) urn = (parsed.scheme, parsed.path) else: urn = (TEL_SCHEME, urn) # assume phone number return Contact.get_or_create(self.org, self.user, name=None, urns=[urn])
def get_or_create_contact(self, urn): if ':' not in urn: urn = URN.from_tel(urn) # assume phone number return Contact.get_or_create(self.org, self.user, name=None, urns=[urn])
def validate_urns(self, value): urn_contacts = [] for urn in value: contact = Contact.get_or_create(self.context['org'], self.context['user'], urns=[urn]) urn_contacts.append(contact) return urn_contacts
def start_call(self, call, to, from_, status_callback): channel = call.channel Contact.get_or_create(channel.org, channel.created_by, urns=[(TEL_SCHEME, to)]) # Verboice differs from Twilio in that they expect the first block of twiml up front payload = unicode(Flow.handle_call(call, {})) # now we can post that to verboice url = "%s?%s" % (self.endpoint, urlencode(dict(channel=self.verboice_channel, address=to))) response = requests.post(url, data=payload, auth=self.auth).json() if 'call_id' not in response: raise IVRException(_('Verboice connection failed.')) # store the verboice call id in our IVRCall call.external_id = response['call_id'] call.status = IN_PROGRESS call.save()
def start_call(self, call, to, from_, status_callback): channel = call.channel Contact.get_or_create(channel.org, channel.created_by, urns=[(TEL_SCHEME, to)]) # Verboice differs from Twilio in that they expect the first block of twiml up front payload = unicode(Flow.handle_call(call, {})) # our config should have our http basic auth parameters and verboice channel config = channel.config_json() # now we can post that to verboice url = "%s?%s" % (self.endpoint, urlencode(dict(channel=config["channel"], address=to))) response = requests.post(url, data=payload, auth=(config["username"], config["password"])).json() # store the verboice call id in our IVRCall call.external_id = response["call_id"] call.status = IN_PROGRESS call.save()
def save(self): """ Update our contact """ name = self.validated_data.get('name') fields = self.validated_data.get('fields') language = self.validated_data.get('language') 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(self.org, self.user, name, urns=self.parsed_urns, language=language) # 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 # 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 _create_contacts(self, count, base_names): """ Creates the given number of contacts with URNs of each type, and fields value for dob and nickname """ contacts = [] for c in range(0, count): name = '%s %d' % (base_names[c % len(base_names)], c + 1) scheme, path, channel = self.urn_generators[c % len(self.urn_generators)](c) contacts.append(Contact.get_or_create(self.org, self.user, name, urns=[':'.join([scheme, path])])) return contacts
def _create_contacts(self, count, base_names): """ Creates the given number of contacts with URNs of each type, and fields value for dob and nickname """ contacts = [] for c in range(0, count): name = '%s %d' % (base_names[c % len(base_names)], c + 1) scheme, path, channel = self.urn_generators[c % len(self.urn_generators)](c) contacts.append(Contact.get_or_create(self.user, self.org, name, urns=[(scheme, path)])) return contacts
def get_or_create_contact(self, urn): if ':' in urn: parsed = ContactURN.parse_urn(urn) urn = (parsed.scheme, parsed.path) else: urn = (TEL_SCHEME, urn) # assume phone number return Contact.get_or_create(self.org, self.user, name=None, urns=[urn])
def create_contact(self, name=None, number=None, twitter=None): """ Create a contact in the master test org """ urns = [] if number: urns.append((TEL_SCHEME, number)) if twitter: urns.append((TWITTER_SCHEME, twitter)) if not name and not urns: raise ValueError("Need a name or URN to create a contact") return Contact.get_or_create(self.user, org=self.org, name=name, urns=urns)
def create_contact(self, name=None, number=None, twitter=None, is_test=False): """ Create a contact in the master test org """ urns = [] if number: urns.append((TEL_SCHEME, number)) if twitter: urns.append((TWITTER_SCHEME, twitter)) if not name and not urns: # pragma: no cover raise ValueError("Need a name or URN to create a contact") return Contact.get_or_create(self.org, self.user, name, urns=urns, is_test=is_test)
def omnibox_deserialize(org, omnibox, user=None): group_ids = [item["id"] for item in omnibox if item["type"] == "group"] contact_ids = [item["id"] for item in omnibox if item["type"] == "contact"] urn_specs = [item["id"] for item in omnibox if item["type"] == "urn"] urns = [] if not org.is_anon: for urn_spec in urn_specs: contact, urn = Contact.get_or_create(org, urn_spec, user) urns.append(urn) return { "groups": ContactGroup.all_groups.filter(uuid__in=group_ids, org=org, is_active=True), "contacts": Contact.objects.filter(uuid__in=contact_ids, org=org, is_active=True), "urns": urns, }
def save(self): urns = self.validated_data.get('urns', []) contacts = self.validated_data.get('contacts', []) groups = self.validated_data.get('groups', []) restart_participants = self.validated_data.get('restart_participants', True) extra = self.validated_data.get('extra') # convert URNs to contacts for urn in urns: contact = Contact.get_or_create(self.context['org'], self.context['user'], urns=[urn]) contacts.append(contact) # ok, let's go create our flow start, the actual starting will happen in our view return FlowStart.create(self.validated_data['flow'], self.context['user'], restart_participants=restart_participants, contacts=contacts, groups=groups, extra=extra)
def save(self): urns = self.validated_data.get('urns', []) contacts = self.validated_data.get('contacts', []) groups = self.validated_data.get('groups', []) restart_participants = self.validated_data.get('restart_participants', True) extra = self.validated_data.get('extra') # convert URNs to contacts for urn in urns: contact = Contact.get_or_create(self.context['org'], self.context['user'], urns=[urn]) contacts.append(contact) # ok, let's go create our flow start, the actual starting will happen in our view return FlowStart.create(self.validated_data['flow'], self.context['user'], restart_participants=restart_participants, contacts=contacts, groups=groups, extra=extra)
def save(self): """ Update our contact """ name = self.validated_data.get('name') language = self.validated_data.get('language') urns = self.validated_data.get('urns') groups = self.validated_data.get('groups') fields = self.validated_data.get('fields') changed = [] if self.instance: # update our name and language if 'name' in self.validated_data and name != self.instance.name: self.instance.name = name changed.append('name') if 'language' in self.validated_data and language != self.instance.language: self.instance.language = language changed.append('language') if 'urns' in self.validated_data and urns is not None: self.instance.update_urns(self.context['user'], urns) if changed: self.instance.save(update_fields=changed) else: if urns is None: # if user is using URN as identifier, ok to create contact from it if they don't already exist urn_as_id = self.validated_data.get('urn') if urn_as_id: urns = [urn_as_id] else: urns = [] self.instance = Contact.get_or_create(self.context['org'], self.context['user'], name, urns=urns, language=language) # update our fields if fields is not None: for key, value in fields.items(): self.instance.set_field(self.context['user'], key, value) # update our groups if groups is not None: self.instance.update_static_groups(self.context['user'], groups) return self.instance
def save(self): """ Update our contact """ name = self.validated_data.get('name') language = self.validated_data.get('language') urns = self.validated_data.get('urns') groups = self.validated_data.get('groups') fields = self.validated_data.get('fields') changed = [] if self.instance: # update our name and language if 'name' in self.validated_data and name != self.instance.name: self.instance.name = name changed.append('name') if 'language' in self.validated_data and language != self.instance.language: self.instance.language = language changed.append('language') if 'urns' in self.validated_data and urns is not None: self.instance.update_urns(self.context['user'], urns) if changed: self.instance.save(update_fields=changed) else: if urns is None: # if user is using URN as identifier, ok to create contact from it if they don't already exist urn_as_id = self.validated_data.get('urn') if urn_as_id: urns = [urn_as_id] else: urns = [] self.instance = Contact.get_or_create(self.context['org'], self.context['user'], name, urns=urns, language=language) # update our fields if fields is not None: for key, value in fields.items(): self.instance.set_field(self.context['user'], key, value) # update our groups if groups is not None: self.instance.update_static_groups(self.context['user'], groups) return self.instance
def create_group_contacts(self, spec, org, user): self._log(f"Generating group contacts...") for g in spec["groups"]: size = int(g.get("size", 0)) if size > 0: group = ContactGroup.user_groups.get(org=org, name=g["name"]) contacts = [] for i in range(size): urn = "tel:+250788%06d" % i contact, _ = Contact.get_or_create(org, urn, user=user) contacts.append(contact) group.update_contacts(user, contacts, True) self._log(self.style.SUCCESS("OK") + "\n")
def save(self): """ Update our contact """ name = self.validated_data.get('name') language = self.validated_data.get('language') urns = self.validated_data.get('urns') groups = self.validated_data.get('groups') custom_fields = self.validated_data.get('fields') changed = [] if self.instance: # update our name and language if 'name' in self.validated_data and name != self.instance.name: self.instance.name = name changed.append('name') if 'language' in self.validated_data and language != self.instance.language: self.instance.language = language changed.append('language') if 'urns' in self.validated_data and urns is not None: self.instance.update_urns(self.context['user'], urns) if changed: self.instance.save(update_fields=changed) else: self.instance = Contact.get_or_create(self.context['org'], self.context['user'], name, urns=urns, language=language) # update our fields if custom_fields is not None: for key, value in six.iteritems(custom_fields): self.instance.set_field(self.context['user'], key, value) # update our groups if groups is not None: self.instance.update_static_groups(self.context['user'], groups) return self.instance
def save(self): """ Create a new broadcast to send out """ from temba.msgs.tasks import send_broadcast_task recipients = self.validated_data.get('contacts', []) + self.validated_data.get('groups', []) for urn in self.validated_data.get('urns', []): # create contacts for URNs if necessary contact = Contact.get_or_create(self.context['org'], self.context['user'], urns=[urn]) contact_urn = contact.urn_objects[urn] recipients.append(contact_urn) # create the broadcast broadcast = Broadcast.create(self.context['org'], self.context['user'], self.validated_data['text'], recipients=recipients, channel=self.validated_data.get('channel')) # send in task send_broadcast_task.delay(broadcast.id) return broadcast
def save(self): urns = self.validated_data.get("urns", []) contacts = self.validated_data.get("contacts", []) groups = self.validated_data.get("groups", []) restart_participants = self.validated_data.get("restart_participants", True) extra = self.validated_data.get("extra") # convert URNs to contacts for urn in urns: contact, urn_obj = Contact.get_or_create(self.context["org"], urn, user=self.context["user"]) contacts.append(contact) # ok, let's go create our flow start, the actual starting will happen in our view return FlowStart.create( self.validated_data["flow"], self.context["user"], restart_participants=restart_participants, contacts=contacts, groups=groups, extra=extra, )
def save(self): """ Create a new broadcast to send out """ from temba.msgs.tasks import send_broadcast_task recipients = self.validated_data.get('contacts', []) + self.validated_data.get('groups', []) for urn in self.validated_data.get('urns', []): # create contacts for URNs if necessary contact = Contact.get_or_create(self.context['org'], self.context['user'], urns=[urn]) contact_urn = contact.urn_objects[urn] recipients.append(contact_urn) # create the broadcast broadcast = Broadcast.create(self.context['org'], self.context['user'], self.validated_data['text'], recipients=recipients, channel=self.validated_data.get('channel')) # send in task send_broadcast_task.delay(broadcast.id) return broadcast
def save(self): """ Update our contact """ name = self.validated_data.get('name') language = self.validated_data.get('language') urns = self.validated_data.get('urns') groups = self.validated_data.get('groups') custom_fields = self.validated_data.get('fields') changed = [] if self.instance: # update our name and language if 'name' in self.validated_data and name != self.instance.name: self.instance.name = name changed.append('name') if 'language' in self.validated_data and language != self.instance.language: self.instance.language = language changed.append('language') if 'urns' in self.validated_data and urns is not None: self.instance.update_urns(self.context['user'], urns) if changed: self.instance.save(update_fields=changed) else: self.instance = Contact.get_or_create(self.context['org'], self.context['user'], name, urns=urns, language=language) # update our fields if custom_fields is not None: for key, value in six.iteritems(custom_fields): self.instance.set_field(self.context['user'], key, value) # update our groups if groups is not None: self.instance.update_static_groups(self.context['user'], groups) return self.instance
def restore_object(self, attrs, instance=None): """ Create a new broadcast to send out """ from temba.msgs.tasks import send_broadcast_task if instance: # pragma: no cover raise ValidationError("Invalid operation") recipients = attrs.get('contacts') + attrs.get('groups') for urn in attrs.get('urns'): # create contacts for URNs if necessary contact = Contact.get_or_create(self.org, self.user, urns=[urn]) contact_urn = contact.urn_objects[urn] recipients.append(contact_urn) # create the broadcast broadcast = Broadcast.create(self.org, self.user, attrs['text'], recipients=recipients, channel=attrs['channel']) # send in task send_broadcast_task.delay(broadcast.id) return broadcast
def post(self, request, *args, **kwargs): from twilio.request_validator import RequestValidator from temba.flows.models import FlowSession signature = request.META.get("HTTP_X_TWILIO_SIGNATURE", "") url = "https://" + request.get_host() + "%s" % request.get_full_path() channel_uuid = kwargs.get("uuid") call_sid = self.get_param("CallSid") direction = self.get_param("Direction") status = self.get_param("CallStatus") to_number = self.get_param("To") to_country = self.get_param("ToCountry") from_number = self.get_param("From") # Twilio sometimes sends un-normalized numbers if to_number and not to_number.startswith("+") and to_country: # pragma: no cover to_number, valid = URN.normalize_number(to_number, to_country) # see if it's a twilio call being initiated if to_number and call_sid and direction == "inbound" and status == "ringing": # find a channel that knows how to answer twilio calls channel = self.get_ringing_channel(uuid=channel_uuid) if not channel: response = VoiceResponse() response.say("Sorry, there is no channel configured to take this call. Goodbye.") response.hangup() return HttpResponse(str(response)) org = channel.org if self.get_channel_type() == "T" and not org.is_connected_to_twilio(): return HttpResponse("No Twilio account is connected", status=400) client = self.get_client(channel=channel) validator = RequestValidator(client.auth[1]) signature = request.META.get("HTTP_X_TWILIO_SIGNATURE", "") url = "https://%s%s" % (request.get_host(), request.get_full_path()) if validator.validate(url, request.POST, signature): from temba.ivr.models import IVRCall # find a contact for the one initiating us urn = URN.from_tel(from_number) contact, urn_obj = Contact.get_or_create(channel.org, urn, channel) flow = Trigger.find_flow_for_inbound_call(contact) if flow: call = IVRCall.create_incoming(channel, contact, urn_obj, channel.created_by, call_sid) session = FlowSession.create(contact, connection=call) call.update_status( request.POST.get("CallStatus", None), request.POST.get("CallDuration", None), "T" ) call.save() FlowRun.create(flow, contact, session=session, connection=call) response = Flow.handle_call(call) return HttpResponse(str(response)) else: # we don't have an inbound trigger to deal with this call. response = channel.generate_ivr_response() # say nothing and hangup, this is a little rude, but if we reject the call, then # they'll get a non-working number error. We send 'busy' when our server is down # so we don't want to use that here either. response.say("") response.hangup() # if they have a missed call trigger, fire that off Trigger.catch_triggers(contact, Trigger.TYPE_MISSED_CALL, channel) # either way, we need to hangup now return HttpResponse(str(response)) # check for call progress events, these include post-call hangup notifications if request.POST.get("CallbackSource", None) == "call-progress-events": if call_sid: from temba.ivr.models import IVRCall call = IVRCall.objects.filter(external_id=call_sid).first() if call: call.update_status( request.POST.get("CallStatus", None), request.POST.get("CallDuration", None), "TW" ) call.save() return HttpResponse("Call status updated") return HttpResponse("No call found") return HttpResponse("Not Handled, unknown action", status=400) # pragma: no cover
def get(self, request, *args, **kwargs): from temba.flows.models import FlowSession from temba.ivr.models import IVRCall action = kwargs["action"].lower() request_body = force_text(request.body) request_path = request.get_full_path() request_method = request.method request_uuid = kwargs["uuid"] if action == "event": if not request_body: return HttpResponse("") body_json = json.loads(request_body) status = body_json.get("status", None) duration = body_json.get("duration", None) call_uuid = body_json.get("uuid", None) conversation_uuid = body_json.get("conversation_uuid", None) if call_uuid is None: return HttpResponse("Missing uuid parameter, ignoring") call = IVRCall.objects.filter(external_id=call_uuid).first() if not call: # try looking up by the conversation uuid (inbound calls start with that) call = IVRCall.objects.filter(external_id=conversation_uuid).first() if call: call.external_id = call_uuid call.save() else: response = dict(message="Call not found for %s" % call_uuid) return JsonResponse(response) channel = call.channel channel_type = channel.channel_type call.update_status(status, duration, channel_type) call.save() response = dict( description="Updated call status", call=dict(status=call.get_status_display(), duration=call.duration) ) event = HttpEvent(request_method, request_path, request_body, 200, json.dumps(response)) ChannelLog.log_ivr_interaction(call, "Updated call status", event) if call.status == IVRCall.COMPLETED: # if our call is completed, hangup runs = FlowRun.objects.filter(connection=call) for run in runs: if not run.is_completed(): run.set_completed(exit_uuid=None) return JsonResponse(response) if action == "answer": if not request_body: return HttpResponse("") body_json = json.loads(request_body) from_number = body_json.get("from", None) channel_number = body_json.get("to", None) external_id = body_json.get("conversation_uuid", None) if not from_number or not channel_number or not external_id: return HttpResponse("Missing parameters, Ignoring") # look up the channel address_q = Q(address=channel_number) | Q(address=("+" + channel_number)) channel = Channel.objects.filter(address_q).filter(is_active=True, channel_type="NX").first() # make sure we got one, and that it matches the key for our org org_uuid = None if channel: org_uuid = channel.org.config.get(NEXMO_UUID, None) if not channel or org_uuid != request_uuid: return HttpResponse("Channel not found for number: %s" % channel_number, status=404) urn = URN.from_tel(from_number) contact, urn_obj = Contact.get_or_create(channel.org, urn, channel) flow = Trigger.find_flow_for_inbound_call(contact) if flow: call = IVRCall.create_incoming(channel, contact, urn_obj, channel.created_by, external_id) session = FlowSession.create(contact, connection=call) FlowRun.create(flow, contact, session=session, connection=call) response = Flow.handle_call(call) channel_type = channel.channel_type call.update_status("answered", None, channel_type) event = HttpEvent(request_method, request_path, request_body, 200, str(response)) ChannelLog.log_ivr_interaction(call, "Incoming request for call", event) return JsonResponse(json.loads(str(response)), safe=False) else: # we don't have an inbound trigger to deal with this call. response = channel.generate_ivr_response() # say nothing and hangup, this is a little rude, but if we reject the call, then # they'll get a non-working number error. We send 'busy' when our server is down # so we don't want to use that here either. response.say("") response.hangup() # if they have a missed call trigger, fire that off Trigger.catch_triggers(contact, Trigger.TYPE_MISSED_CALL, channel) # either way, we need to hangup now return JsonResponse(json.loads(str(response)), safe=False)
class USSDSession(ChannelSession): USSD_PULL = INCOMING = 'I' USSD_PUSH = OUTGOING = 'O' objects = USSDQuerySet.as_manager() class Meta: proxy = True @property def should_end(self): return self.status == self.ENDING def mark_ending(self): # session to be ended if self.status != self.ENDING: self.status = self.ENDING self.save(update_fields=['status']) def close(self): # session has successfully ended if self.status == self.ENDING: self.status = self.COMPLETED else: self.status = self.INTERRUPTED self.ended_on = timezone.now() self.save(update_fields=['status', 'ended_on']) def start_async(self, flow, date, message_id): from temba.msgs.models import Msg, USSD message = Msg.objects.create(channel=self.channel, contact=self.contact, contact_urn=self.contact_urn, sent_on=date, connection=self, msg_type=USSD, external_id=message_id, created_on=timezone.now(), modified_on=timezone.now(), org=self.channel.org, direction=self.INCOMING) flow.start([], [self.contact], start_msg=message, restart_participants=True, connection=self) def handle_async(self, urn, content, date, message_id): from temba.msgs.models import Msg, USSD Msg.create_incoming(channel=self.channel, org=self.org, urn=urn, text=content or '', date=date, connection=self, msg_type=USSD, external_id=message_id) def handle_sync(self): # pragma: needs cover # TODO: implement for InfoBip and other sync APIs pass @classmethod def handle_incoming(cls, channel, urn, date, external_id, contact=None, message_id=None, status=None, content=None, starcode=None, org=None, async=True): trigger = None contact_urn = None # handle contact with channel urn = URN.from_tel(urn) if not contact: contact, contact_urn = Contact.get_or_create( channel.org, urn, channel) elif urn: contact_urn = ContactURN.get_or_create(org, contact, urn, channel=channel) contact.set_preferred_channel(channel) if contact_urn: contact_urn.update_affinity(channel) # setup session defaults = dict(channel=channel, contact=contact, contact_urn=contact_urn, org=channel.org if channel else contact.org) if status == cls.TRIGGERED: trigger = Trigger.find_trigger_for_ussd_session(contact, starcode) if not trigger: return False defaults.update( dict(started_on=date, direction=cls.USSD_PULL, status=status)) elif status == cls.INTERRUPTED: defaults.update(dict(ended_on=date, status=status)) else: defaults.update(dict(status=cls.IN_PROGRESS)) # check if there's an initiated PUSH connection connection = cls.objects.get_initiated_push(contact) created = False if not connection: try: connection = cls.objects.select_for_update().exclude(status__in=ChannelSession.DONE)\ .get(external_id=external_id) created = False for k, v in six.iteritems(defaults): setattr(connection, k, v() if callable(v) else v) connection.save() except cls.DoesNotExist: defaults['external_id'] = external_id connection = cls.objects.create(**defaults) FlowSession.create(contact, connection=connection) created = True else: defaults.update(dict(external_id=external_id)) for key, value in six.iteritems(defaults): setattr(connection, key, value) connection.save() created = None # start session if created and async and trigger: connection.start_async(trigger.flow, date, message_id)
class USSDSession(ChannelSession): USSD_PULL = INCOMING = 'I' USSD_PUSH = OUTGOING = 'O' objects = USSDQuerySet.as_manager() class Meta: proxy = True def start_session_async(self, flow, urn, content, date, message_id): from temba.msgs.models import Msg, USSD message = Msg.objects.create(channel=self.channel, contact=self.contact, contact_urn=self.contact_urn, sent_on=date, session=self, msg_type=USSD, external_id=message_id, created_on=timezone.now(), modified_on=timezone.now(), org=self.channel.org, direction=self.INCOMING) flow.start([], [self.contact], start_msg=message, restart_participants=True, session=self) def handle_session_async(self, urn, content, date, message_id): from temba.msgs.models import Msg, USSD Msg.create_incoming(channel=self.channel, urn=urn, text=content or '', date=date, session=self, msg_type=USSD, external_id=message_id) def handle_ussd_session_sync(self): # pragma: needs cover # TODO: implement for InfoBip and other sync APIs pass @classmethod def handle_incoming(cls, channel, urn, date, external_id, message_id=None, status=None, flow=None, content=None, starcode=None, org=None, async=True): trigger = None # handle contact with channel urn = URN.from_tel(urn) contact = Contact.get_or_create(channel.org, channel.created_by, urns=[urn], channel=channel) contact_urn = contact.urn_objects[urn] contact.set_preferred_channel(channel) contact_urn.update_affinity(channel) # setup session defaults = dict(channel=channel, contact=contact, contact_urn=contact_urn, org=channel.org) if status == cls.TRIGGERED: trigger = Trigger.find_trigger_for_ussd_session(contact, starcode) if not trigger: return False defaults.update( dict(started_on=date, direction=cls.USSD_PULL, status=status)) elif status == cls.INTERRUPTED: defaults.update(dict(ended_on=date, status=status)) else: defaults.update(dict(status=USSDSession.IN_PROGRESS)) # check if there's an initiated PUSH session session = cls.objects.get_initiated_push_session(contact) if not session: session, created = cls.objects.update_or_create( external_id=external_id, defaults=defaults) else: defaults.update(dict(external_id=external_id)) for key, value in six.iteritems(defaults): setattr(session, key, value) session.save() created = None # start session if created and async and trigger: session.start_session_async(trigger.flow, urn, content, date, message_id)
def get_or_create_contact(self, urn): if ':' not in urn: urn = URN.from_tel(urn) # assume phone number return Contact.get_or_create(self.org, self.user, name=None, urns=[urn])
def test_event_deliveries(self): sms = self.create_msg(contact=self.joe, direction="I", status="H", text="I'm gonna pop some tags") with patch("requests.Session.send") as mock: now = timezone.now() mock.return_value = MockResponse(200, "Hello World") # trigger an event, shouldnn't fire as we don't have a webhook WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms, now) self.assertFalse(WebHookEvent.objects.all()) self.setupChannel() with patch("requests.Session.send") as mock: # clear out which events we listen for, we still shouldnt be notified though we have a webhook self.channel.org.webhook_events = 0 self.channel.org.save() now = timezone.now() mock.return_value = MockResponse(200, "Hello World") # trigger an event, shouldnn't fire as we don't have a webhook WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms, now) self.assertFalse(WebHookEvent.objects.all()) self.setupChannel() with patch("requests.Session.send") as mock: # remove all the org users self.org.administrators.clear() self.org.editors.clear() self.org.viewers.clear() mock.return_value = MockResponse(200, "Hello World") # trigger an event WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms, now) event = WebHookEvent.objects.get() self.assertEqual("F", event.status) self.assertEqual(0, event.try_count) self.assertFalse(event.next_attempt) result = WebHookResult.objects.get() self.assertIn("No active user", result.message) self.assertEqual(0, result.status_code) self.assertFalse(mock.called) # what if they send weird json back? self.release(WebHookEvent.objects.all()) # add ad manager back in self.org.administrators.add(self.admin) self.admin.set_org(self.org) with patch("requests.Session.send") as mock: mock.return_value = MockResponse(200, "Hello World") # trigger an event WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms, now) event = WebHookEvent.objects.get() self.assertEqual("C", event.status) self.assertEqual(1, event.try_count) self.assertFalse(event.next_attempt) result = WebHookResult.objects.get() self.assertIn("Event delivered successfully", result.message) self.assertIn("not JSON", result.message) self.assertEqual(200, result.status_code) self.assertTrue(mock.called) self.release(WebHookEvent.objects.all()) with patch("requests.Session.send") as mock: mock.side_effect = [MockResponse(500, "I am error")] # trigger an event WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms, now) event = WebHookEvent.objects.all().first() self.assertEqual("E", event.status) self.assertEqual(1, event.try_count) self.assertTrue(event.next_attempt) mock.return_value = MockResponse(200, "Hello World") # simulate missing channel event.channel = None event.save() # no exception should raised event.deliver() self.assertTrue(mock.called) self.assertEqual(mock.call_count, 2) self.release(WebHookEvent.objects.all()) with patch("requests.Session.send") as mock: # valid json, but not our format bad_json = '{ "thrift_shops": ["Goodwill", "Value Village"] }' mock.return_value = MockResponse(200, bad_json) WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms, now) event = WebHookEvent.objects.get() self.assertEqual("C", event.status) self.assertEqual(1, event.try_count) self.assertFalse(event.next_attempt) self.assertTrue(mock.called) result = WebHookResult.objects.get() self.assertIn("Event delivered successfully", result.message) self.assertIn("ignoring", result.message) self.assertEqual(200, result.status_code) self.assertEqual(bad_json, result.body) self.release(WebHookEvent.objects.all()) with patch("requests.Session.send") as mock: mock.return_value = MockResponse(200, '{ "phone": "+250788123123", "text": "I am success" }') WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms, now) event = WebHookEvent.objects.get() self.assertEqual("C", event.status) self.assertEqual(1, event.try_count) self.assertFalse(event.next_attempt) result = WebHookResult.objects.get() self.assertEqual(200, result.status_code) self.assertTrue(mock.called) broadcast = Broadcast.objects.get() contact, urn_obj = Contact.get_or_create(self.org, "tel:+250788123123", self.channel, user=self.admin) self.assertTrue(broadcast.text, {"base": "I am success"}) self.assertTrue(contact, broadcast.contacts.all()) self.assertTrue(mock.called) args = mock.call_args_list[0][0] prepared_request = args[0] self.assertEqual(self.org.get_webhook_url(), prepared_request.url) data = parse_qs(prepared_request.body) self.assertEqual(self.joe.get_urn(TEL_SCHEME).path, data["phone"][0]) self.assertEqual(str(self.joe.get_urn(TEL_SCHEME)), data["urn"][0]) self.assertEqual(self.joe.uuid, data["contact"][0]) self.assertEqual(self.joe.name, data["contact_name"][0]) self.assertEqual(sms.pk, int(data["sms"][0])) self.assertEqual(self.channel.pk, int(data["channel"][0])) self.assertEqual(WebHookEvent.TYPE_SMS_RECEIVED, data["event"][0]) self.assertEqual("I'm gonna pop some tags", data["text"][0]) self.assertIn("time", data) self.release(WebHookEvent.objects.all()) with patch("requests.Session.send") as mock: mock.return_value = MockResponse(500, "I am error") next_attempt_earliest = timezone.now() + timedelta(minutes=4) next_attempt_latest = timezone.now() + timedelta(minutes=6) WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms, now) event = WebHookEvent.objects.get() self.assertEqual("E", event.status) self.assertEqual(1, event.try_count) self.assertTrue(event.next_attempt) self.assertTrue(next_attempt_earliest < event.next_attempt and next_attempt_latest > event.next_attempt) result = WebHookResult.objects.get() self.assertIn("Error", result.message) self.assertEqual(500, result.status_code) self.assertEqual("I am error", result.body) # make sure things become failures after three retries event.try_count = 2 event.deliver() event.save() self.assertTrue(mock.called) self.assertEqual("F", event.status) self.assertEqual(3, event.try_count) self.assertFalse(event.next_attempt) result = WebHookResult.objects.get() self.assertIn("Error", result.message) self.assertEqual(500, result.status_code) self.assertEqual("I am error", result.body) self.assertEqual("http://fake.com/webhook.php", result.url) self.assertTrue(result.data.find("pop+some+tags") > 0) # check out our api log response = self.client.get(reverse("api.log")) self.assertRedirect(response, reverse("users.user_login")) response = self.client.get(reverse("api.log_read", args=[event.pk])) self.assertRedirect(response, reverse("users.user_login")) self.release(WebHookEvent.objects.all()) # add a webhook header to the org self.channel.org.webhook = { "url": "http://fake.com/webhook.php", "headers": {"X-My-Header": "foobar", "Authorization": "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="}, "method": "POST", } self.channel.org.save() # check that our webhook settings have saved self.assertEqual("http://fake.com/webhook.php", self.channel.org.get_webhook_url()) self.assertDictEqual( {"X-My-Header": "foobar", "Authorization": "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="}, self.channel.org.get_webhook_headers(), ) with patch("requests.Session.send") as mock: mock.return_value = MockResponse(200, "Boom") WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms, now) event = WebHookEvent.objects.get() result = WebHookResult.objects.get() # both headers should be in the json-encoded url string self.assertIn("X-My-Header: foobar", result.request) self.assertIn("Authorization: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", result.request)
def handle_incoming( cls, channel, urn, date, external_id, contact=None, message_id=None, status=None, content=None, starcode=None, org=None, do_async=True, ): trigger = None contact_urn = None # handle contact with channel urn = URN.from_tel(urn) if not contact: contact, contact_urn = Contact.get_or_create(channel.org, urn, channel) elif urn: contact_urn = ContactURN.get_or_create(org, contact, urn, channel=channel) contact.set_preferred_channel(channel) if contact_urn: contact_urn.update_affinity(channel) # setup session defaults = dict( channel=channel, contact=contact, contact_urn=contact_urn, org=channel.org if channel else contact.org ) if status == cls.TRIGGERED: trigger = Trigger.find_trigger_for_ussd_session(contact, starcode) if not trigger: return False defaults.update(dict(started_on=date, direction=cls.USSD_PULL, status=status)) elif status == cls.INTERRUPTED: defaults.update(dict(ended_on=date, status=status)) else: defaults.update(dict(status=cls.IN_PROGRESS)) # check if there's an initiated PUSH connection connection = cls.objects.get_initiated_push(contact) created = False if not connection: try: connection = ( cls.objects.select_for_update() .exclude(status__in=ChannelConnection.DONE) .get(external_id=external_id) ) created = False for k, v in defaults.items(): setattr(connection, k, v() if callable(v) else v) connection.save() except cls.DoesNotExist: defaults["external_id"] = external_id connection = cls.objects.create(**defaults) FlowSession.create(contact, connection=connection) created = True else: defaults.update(dict(external_id=external_id)) for key, value in defaults.items(): setattr(connection, key, value) connection.save() created = None # start session if created and do_async and trigger: connection.start_async(trigger.flow, date, message_id) # resume session, deal with incoming content and all the other states else: connection.handle_async(urn, content, date, message_id) return connection
def test_event_deliveries(self): sms = self.create_msg(contact=self.joe, direction='I', status='H', text="I'm gonna pop some tags") with patch('requests.Session.send') as mock: now = timezone.now() mock.return_value = MockResponse(200, "Hello World") # trigger an event, shouldnn't fire as we don't have a webhook WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now) self.assertFalse(WebHookEvent.objects.all()) self.setupChannel() with patch('requests.Session.send') as mock: # clear out which events we listen for, we still shouldnt be notified though we have a webhook self.channel.org.webhook_events = 0 self.channel.org.save() now = timezone.now() mock.return_value = MockResponse(200, "Hello World") # trigger an event, shouldnn't fire as we don't have a webhook WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now) self.assertFalse(WebHookEvent.objects.all()) self.setupChannel() with patch('requests.Session.send') as mock: # remove all the org users self.org.administrators.clear() self.org.editors.clear() self.org.viewers.clear() mock.return_value = MockResponse(200, "Hello World") # trigger an event WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now) event = WebHookEvent.objects.get() self.assertEquals('F', event.status) self.assertEquals(0, event.try_count) self.assertFalse(event.next_attempt) result = WebHookResult.objects.get() self.assertStringContains("No active user", result.message) self.assertEquals(0, result.status_code) self.assertFalse(mock.called) # what if they send weird json back? WebHookEvent.objects.all().delete() WebHookResult.objects.all().delete() # add ad manager back in self.org.administrators.add(self.admin) self.admin.set_org(self.org) with patch('requests.Session.send') as mock: mock.return_value = MockResponse(200, "Hello World") # trigger an event WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now) event = WebHookEvent.objects.get() self.assertEquals('C', event.status) self.assertEquals(1, event.try_count) self.assertFalse(event.next_attempt) result = WebHookResult.objects.get() self.assertStringContains("Event delivered successfully", result.message) self.assertStringContains("not JSON", result.message) self.assertEquals(200, result.status_code) self.assertTrue(mock.called) WebHookEvent.objects.all().delete() WebHookResult.objects.all().delete() with patch('requests.Session.send') as mock: # valid json, but not our format bad_json = '{ "thrift_shops": ["Goodwill", "Value Village"] }' mock.return_value = MockResponse(200, bad_json) WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now) event = WebHookEvent.objects.get() self.assertEquals('C', event.status) self.assertEquals(1, event.try_count) self.assertFalse(event.next_attempt) self.assertTrue(mock.called) result = WebHookResult.objects.get() self.assertStringContains("Event delivered successfully", result.message) self.assertStringContains("ignoring", result.message) self.assertEquals(200, result.status_code) self.assertEquals(bad_json, result.body) WebHookEvent.objects.all().delete() WebHookResult.objects.all().delete() with patch('requests.Session.send') as mock: mock.return_value = MockResponse(200, '{ "phone": "+250788123123", "text": "I am success" }') WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now) event = WebHookEvent.objects.get() self.assertEquals('C', event.status) self.assertEquals(1, event.try_count) self.assertFalse(event.next_attempt) result = WebHookResult.objects.get() self.assertEquals(200, result.status_code) self.assertTrue(mock.called) broadcast = Broadcast.objects.get() contact = Contact.get_or_create(self.org, self.admin, name=None, urns=["tel:+250788123123"], channel=self.channel) self.assertTrue("I am success", broadcast.text) self.assertTrue(contact, broadcast.contacts.all()) self.assertTrue(mock.called) args = mock.call_args_list[0][0] prepared_request = args[0] self.assertEquals(self.org.get_webhook_url(), prepared_request.url) data = parse_qs(prepared_request.body) self.assertEquals(self.joe.get_urn(TEL_SCHEME).path, data['phone'][0]) self.assertEquals(unicode(self.joe.get_urn(TEL_SCHEME)), data['urn'][0]) self.assertEquals(self.joe.uuid, data['contact'][0]) self.assertEquals(self.joe.name, data['contact_name'][0]) self.assertEquals(sms.pk, int(data['sms'][0])) self.assertEquals(self.channel.pk, int(data['channel'][0])) self.assertEquals(SMS_RECEIVED, data['event'][0]) self.assertEquals("I'm gonna pop some tags", data['text'][0]) self.assertTrue('time' in data) WebHookEvent.objects.all().delete() WebHookResult.objects.all().delete() with patch('requests.Session.send') as mock: mock.return_value = MockResponse(500, "I am error") next_attempt_earliest = timezone.now() + timedelta(minutes=4) next_attempt_latest = timezone.now() + timedelta(minutes=6) WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now) event = WebHookEvent.objects.get() self.assertEquals('E', event.status) self.assertEquals(1, event.try_count) self.assertTrue(event.next_attempt) self.assertTrue(next_attempt_earliest < event.next_attempt and next_attempt_latest > event.next_attempt) result = WebHookResult.objects.get() self.assertStringContains("Error", result.message) self.assertEquals(500, result.status_code) self.assertEquals("I am error", result.body) # make sure things become failures after three retries event.try_count = 2 event.deliver() event.save() self.assertTrue(mock.called) self.assertEquals('F', event.status) self.assertEquals(3, event.try_count) self.assertFalse(event.next_attempt) result = WebHookResult.objects.get() self.assertStringContains("Error", result.message) self.assertEquals(500, result.status_code) self.assertEquals("I am error", result.body) self.assertEquals("http://fake.com/webhook.php", result.url) self.assertTrue(result.data.find("pop+some+tags") > 0) # check out our api log response = self.client.get(reverse('api.log')) self.assertRedirect(response, reverse('users.user_login')) response = self.client.get(reverse('api.log_read', args=[event.pk])) self.assertRedirect(response, reverse('users.user_login')) WebHookEvent.objects.all().delete() WebHookResult.objects.all().delete() # add a webhook header to the org self.channel.org.webhook = u'{"url": "http://fake.com/webhook.php", "headers": {"X-My-Header": "foobar", "Authorization": "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="}, "method": "POST"}' self.channel.org.save() # check that our webhook settings have saved self.assertEquals('http://fake.com/webhook.php', self.channel.org.get_webhook_url()) self.assertDictEqual({'X-My-Header': 'foobar', 'Authorization': 'Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='}, self.channel.org.get_webhook_headers()) with patch('requests.Session.send') as mock: mock.return_value = MockResponse(200, "Boom") WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now) event = WebHookEvent.objects.get() result = WebHookResult.objects.get() # both headers should be in the json-encoded url string self.assertStringContains('X-My-Header: foobar', result.request) self.assertStringContains('Authorization: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', result.request)
def test_event_deliveries(self): sms = self.create_msg(contact=self.joe, direction='I', status='H', text="I'm gonna pop some tags") with patch('requests.Session.send') as mock: now = timezone.now() mock.return_value = MockResponse(200, "Hello World") # trigger an event, shouldnn't fire as we don't have a webhook WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now) self.assertFalse(WebHookEvent.objects.all()) self.setupChannel() with patch('requests.Session.send') as mock: # clear out which events we listen for, we still shouldnt be notified though we have a webhook self.channel.org.webhook_events = 0 self.channel.org.save() now = timezone.now() mock.return_value = MockResponse(200, "Hello World") # trigger an event, shouldnn't fire as we don't have a webhook WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now) self.assertFalse(WebHookEvent.objects.all()) self.setupChannel() with patch('requests.Session.send') as mock: # remove all the org users self.org.administrators.clear() self.org.editors.clear() self.org.viewers.clear() mock.return_value = MockResponse(200, "Hello World") # trigger an event WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now) event = WebHookEvent.objects.get() self.assertEquals('F', event.status) self.assertEquals(0, event.try_count) self.assertFalse(event.next_attempt) result = WebHookResult.objects.get() self.assertStringContains("No active user", result.message) self.assertEquals(0, result.status_code) self.assertFalse(mock.called) # what if they send weird json back? WebHookEvent.objects.all().delete() WebHookResult.objects.all().delete() # add ad manager back in self.org.administrators.add(self.admin) self.admin.set_org(self.org) with patch('requests.Session.send') as mock: mock.return_value = MockResponse(200, "Hello World") # trigger an event WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now) event = WebHookEvent.objects.get() self.assertEquals('C', event.status) self.assertEquals(1, event.try_count) self.assertFalse(event.next_attempt) result = WebHookResult.objects.get() self.assertStringContains("Event delivered successfully", result.message) self.assertStringContains("not JSON", result.message) self.assertEquals(200, result.status_code) self.assertTrue(mock.called) WebHookEvent.objects.all().delete() WebHookResult.objects.all().delete() with patch('requests.Session.send') as mock: # valid json, but not our format bad_json = '{ "thrift_shops": ["Goodwill", "Value Village"] }' mock.return_value = MockResponse(200, bad_json) WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now) event = WebHookEvent.objects.get() self.assertEquals('C', event.status) self.assertEquals(1, event.try_count) self.assertFalse(event.next_attempt) self.assertTrue(mock.called) result = WebHookResult.objects.get() self.assertStringContains("Event delivered successfully", result.message) self.assertStringContains("ignoring", result.message) self.assertEquals(200, result.status_code) self.assertEquals(bad_json, result.body) WebHookEvent.objects.all().delete() WebHookResult.objects.all().delete() with patch('requests.Session.send') as mock: mock.return_value = MockResponse(200, '{ "phone": "+250788123123", "text": "I am success" }') WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now) event = WebHookEvent.objects.get() self.assertEquals('C', event.status) self.assertEquals(1, event.try_count) self.assertFalse(event.next_attempt) result = WebHookResult.objects.get() self.assertEquals(200, result.status_code) self.assertTrue(mock.called) broadcast = Broadcast.objects.get() contact = Contact.get_or_create(self.org, self.admin, name=None, urns=[(TEL_SCHEME, "+250788123123")], incoming_channel=self.channel) self.assertTrue("I am success", broadcast.text) self.assertTrue(contact, broadcast.contacts.all()) self.assertTrue(mock.called) args = mock.call_args_list[0][0] prepared_request = args[0] self.assertEquals(self.org.get_webhook_url(), prepared_request.url) data = parse_qs(prepared_request.body) self.assertEquals(self.joe.get_urn(TEL_SCHEME).path, data['phone'][0]) self.assertEquals(sms.pk, int(data['sms'][0])) self.assertEquals(self.channel.pk, int(data['channel'][0])) self.assertEquals(SMS_RECEIVED, data['event'][0]) self.assertEquals("I'm gonna pop some tags", data['text'][0]) self.assertTrue('time' in data) WebHookEvent.objects.all().delete() WebHookResult.objects.all().delete() with patch('requests.Session.send') as mock: mock.return_value = MockResponse(500, "I am error") next_attempt_earliest = timezone.now() + timedelta(minutes=4) next_attempt_latest = timezone.now() + timedelta(minutes=6) WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now) event = WebHookEvent.objects.get() self.assertEquals('E', event.status) self.assertEquals(1, event.try_count) self.assertTrue(event.next_attempt) self.assertTrue(next_attempt_earliest < event.next_attempt and next_attempt_latest > event.next_attempt) result = WebHookResult.objects.get() self.assertStringContains("Error", result.message) self.assertEquals(500, result.status_code) self.assertEquals("I am error", result.body) # make sure things become failures after three retries event.try_count = 2 event.deliver() event.save() self.assertTrue(mock.called) self.assertEquals('F', event.status) self.assertEquals(3, event.try_count) self.assertFalse(event.next_attempt) result = WebHookResult.objects.get() self.assertStringContains("Error", result.message) self.assertEquals(500, result.status_code) self.assertEquals("I am error", result.body) self.assertEquals("http://fake.com/webhook.php", result.url) self.assertTrue(result.data.find("pop+some+tags") > 0) # check out our api log response = self.client.get(reverse('api.log')) self.assertRedirect(response, reverse('users.user_login')) response = self.client.get(reverse('api.log_read', args=[event.pk])) self.assertRedirect(response, reverse('users.user_login')) WebHookEvent.objects.all().delete() WebHookResult.objects.all().delete() # add a webhook header to the org self.channel.org.webhook = u'{"url": "http://fake.com/webhook.php", "headers": {"X-My-Header": "foobar", "Authorization": "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="}, "method": "POST"}' self.channel.org.save() # check that our webhook settings have saved self.assertEquals('http://fake.com/webhook.php', self.channel.org.get_webhook_url()) self.assertDictEqual({'X-My-Header': 'foobar', 'Authorization': 'Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='}, self.channel.org.get_webhook_headers()) with patch('requests.Session.send') as mock: mock.return_value = MockResponse(200, "Boom") WebHookEvent.trigger_sms_event(SMS_RECEIVED, sms, now) event = WebHookEvent.objects.get() result = WebHookResult.objects.get() # both headers should be in the json-encoded url string self.assertStringContains('X-My-Header: foobar', result.request) self.assertStringContains('Authorization: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', result.request)
def get_contact(self, number): return Contact.get_or_create(self.org, self.user, name=None, urns=[(TEL_SCHEME, number)])
def restore_object(self, attrs, instance=None): """ Update our contact """ if instance: # pragma: no cover raise ValidationError("Invalid operation") org = self.user.get_org() if org.is_anon: raise ValidationError("Cannot update contacts on anonymous organizations") uuid = attrs.get('uuid', None) if uuid: contact = Contact.objects.get(uuid=uuid, org=org, is_active=True) urns = attrs.get('urns', None) phone = attrs.get('phone', None) # user didn't specify either urns or phone, stick to what already exists if urns is None and phone is None: urns = [(u.scheme, u.path) for u in contact.urns.all()] # user only specified phone, build our urns from it if phone: urns = [(TEL_SCHEME, attrs['phone'])] if uuid: contact.update_urns(urns) else: contact = Contact.get_or_create(self.user, org, urns=urns, uuid=uuid) changed = [] # update our name and language if attrs.get('name', None): contact.name = attrs['name'] changed.append('name') if 'language' in attrs: contact.language = attrs['language'] changed.append('language') # save our contact if it changed if changed: contact.save(update_fields=changed) # update our fields fields = attrs.get('fields', None) if not fields is None: for key, value in fields.items(): existing_by_key = ContactField.objects.filter(org=self.user.get_org(), key__iexact=key, is_active=True).first() if existing_by_key: contact.set_field(existing_by_key.key, value) continue # TODO as above, need to get users to stop updating via label existing_by_label = ContactField.objects.filter(org=self.user.get_org(), label__iexact=key, is_active=True).first() if existing_by_label: contact.set_field(existing_by_label.key, value) # update our groups by UUID or name (deprecated) group_uuids = attrs.get('group_uuids', None) group_names = attrs.get('groups', None) if not group_uuids is None: contact.update_groups(group_uuids) elif not group_names is None: # by name creates groups if necessary groups = [ContactGroup.get_or_create(self.user.get_org(), self.user, name) for name in group_names] contact.update_groups(groups) return contact