def test_end_date(self): daily = Archive.objects.create( org=self.org, archive_type=Archive.TYPE_FLOWRUN, size=10, hash=uuid4().hex, url=f"http://s3-bucket.aws.com/my/32562662.jsonl.gz", record_count=100, start_date=date(2018, 2, 1), period="D", build_time=1234, needs_deletion=True, ) monthly = Archive.objects.create( org=self.org, archive_type=Archive.TYPE_FLOWRUN, size=10, hash=uuid4().hex, url=f"http://s3-bucket.aws.com/my/32562663.jsonl.gz", record_count=2000, start_date=date(2018, 1, 1), period="M", build_time=1234, needs_deletion=False, ) self.assertEqual(date(2018, 2, 2), daily.get_end_date()) self.assertEqual(date(2018, 2, 1), monthly.get_end_date()) # check the start date of our db data self.assertEqual(date(2018, 2, 1), self.org.get_delete_date(archive_type=Archive.TYPE_FLOWRUN))
def form_valid(self, form): from .type import RocketChatType base_url = form.cleaned_data["base_url"] config = { RocketChatType.CONFIG_BASE_URL: base_url, RocketChatType.CONFIG_SECRET: form.cleaned_data["secret"], RocketChatType.CONFIG_ADMIN_AUTH_TOKEN: form.cleaned_data["admin_auth_token"], RocketChatType.CONFIG_ADMIN_USER_ID: form.cleaned_data["admin_user_id"], } rc_host = urlparse(base_url).netloc self.object = Ticketer( uuid=uuid4(), org=self.org, ticketer_type=RocketChatType.slug, config=config, name=rc_host, created_by=self.request.user, modified_by=self.request.user, ) client = Client(config[RocketChatType.CONFIG_BASE_URL], config[RocketChatType.CONFIG_SECRET]) webhook_url = WEBHOOK_URL_TEMPLATE.format(domain=self.object.org.get_brand_domain(), uuid=self.object.uuid) try: client.settings(webhook_url) self.request.session.pop(self.SESSION_KEY, None) except ClientError as err: messages.error(self.request, err.msg if err.msg else _("Configuration has failed")) return super().get(self.request, *self.args, **self.kwargs) self.object.save() return super().form_valid(form)
def create_archive(self, archive_type, period, start_date, records=(), needs_deletion=False, rollup_of=(), s3=None, org=None): archive_hash = uuid4().hex bucket = "s3-bucket" key = f"things/{archive_hash}.jsonl.gz" if s3: s3.put_jsonl(bucket, key, records) archive = Archive.objects.create( org=org or self.org, archive_type=archive_type, size=10, hash=archive_hash, url=f"http://{bucket}.aws.com/{key}", record_count=len(records), start_date=start_date, period=period, build_time=23425, needs_deletion=needs_deletion, ) if rollup_of: Archive.objects.filter(id__in=[a.id for a in rollup_of]).update( rollup=archive) return archive
def __init__(self, contact, flow, start=None): self.org = contact.org self.contact = contact self.start = start self.session = None contact_def = { "uuid": str(self.contact.uuid), "name": self.contact.name, "language": self.contact.language } self.output = { "uuid": str(uuid4()), "type": Flow.GOFLOW_TYPES[flow.flow_type], "environment": self.org.as_environment_def(), "trigger": { "type": "manual", "flow": flow.as_export_ref(), "contact": contact_def, "triggered_on": self._now(), }, "contact": contact_def, "runs": [{ "uuid": str(uuid4()), "flow": flow.as_export_ref(), "path": [], "events": [], "results": {}, "status": "active", "created_on": self._now(), "modified_on": self._now(), "exited_on": None, }], "status": "active", } self.current_run = self.output["runs"][0] self.current_node = None self.events = []
def create(cls, org, user, ticketer_type, name, config): return cls.objects.create( uuid=uuid4(), ticketer_type=ticketer_type, name=name, config=config, org=org, created_by=user, modified_by=user, )
def form_valid(self, form): user = self.request.user org = user.get_org() data = form.cleaned_data country = data.get("country") number = data.get("number") url = data.get("url") role = data.get("role") config = { Channel.CONFIG_SEND_URL: url, Channel.CONFIG_ACCOUNT_SID: data.get("account_sid", None), Channel.CONFIG_AUTH_TOKEN: data.get("account_token", str(uuid4())), Channel.CONFIG_CALLBACK_DOMAIN: org.get_brand_domain(), Channel.CONFIG_MAX_CONCURRENT_EVENTS: data.get("max_concurrent_events", None), } is_short_code = len(number) <= 6 if not is_short_code: phone_number = phonenumbers.parse(number=number, region=country) address = f"+{str(phone_number.country_code)}{str(phone_number.national_number)}" name = phonenumbers.format_number( phonenumbers.parse(address, None), phonenumbers.PhoneNumberFormat.NATIONAL) else: role = Channel.ROLE_SEND + Channel.ROLE_RECEIVE address = number name = number self.object = Channel.create(org, user, country, "TW", name=name, address=address, config=config, role=role) if not data.get("account_sid", None): config[ Channel. CONFIG_ACCOUNT_SID] = f"{self.request.branding['name'].lower()}_{self.object.pk}" self.object.config = config self.object.save(update_fields=("config", )) return super().form_valid(form)
def send_msg(self, text, channel=None, attachments=[]): msg = { "uuid": str(uuid4()), "urn": self.contact.get_urn().urn, "text": text, "channel": self._channel_ref(channel), } if attachments: msg["attachments"] = attachments self._log_event("msg_created", msg=msg) return self
def create_incoming_call(self, flow, contact, status=IVRCall.STATUS_COMPLETED): """ Create something that looks like an incoming IVR call handled by mailroom """ call = IVRCall.objects.create( org=self.org, channel=self.channel, direction=IVRCall.DIRECTION_IN, contact=contact, contact_urn=contact.get_urn(), status=status, duration=15, ) session = FlowSession.objects.create(uuid=uuid4(), org=contact.org, contact=contact, connection=call) FlowRun.objects.create(org=self.org, flow=flow, contact=contact, connection=call, session=session) Msg.objects.create( org=self.org, channel=self.channel, connection=call, direction="O", contact=contact, contact_urn=contact.get_urn(), text="Hello", status="S", sent_on=timezone.now(), created_on=timezone.now(), ) ChannelLog.objects.create( channel=self.channel, connection=call, request='{"say": "Hello"}', response='{"status": "%s"}' % ("error" if status == IVRCall.STATUS_FAILED else "OK"), url="https://acme-calls.com/reply", method="POST", is_error=status == IVRCall.STATUS_FAILED, response_status=200, description="Looks good", ) return call
def create_flow(self, definition=None, **kwargs): if "org" not in kwargs: kwargs["org"] = self.org if "user" not in kwargs: kwargs["user"] = self.user if "name" not in kwargs: kwargs["name"] = "Color Flow" flow = Flow.create(**kwargs) if not definition: # if definition isn't provided, generate simple single message flow node_uuid = str(uuid4()) definition = { "version": "10", "flow_type": "F", "base_language": "eng", "entry": node_uuid, "action_sets": [{ "uuid": node_uuid, "x": 0, "y": 0, "actions": [{ "msg": { "eng": "Hey everybody!" }, "media": {}, "send_all": False, "type": "reply" }], "destination": None, }], "rule_sets": [], } flow.version_number = definition["version"] flow.save() json_flow = FlowRevision.migrate_definition( definition, flow, to_version=Flow.FINAL_LEGACY_VERSION) flow.update(json_flow) return flow
def form_valid(self, form): from .type import RocketChatType base_url = form.cleaned_data["base_url"] bot_username = form.cleaned_data["bot_username"] admin_auth_token = form.cleaned_data["admin_auth_token"] admin_user_id = form.cleaned_data["admin_user_id"] secret = form.cleaned_data["secret"] config = { RocketChatType.CONFIG_BASE_URL: base_url, RocketChatType.CONFIG_BOT_USERNAME: bot_username, RocketChatType.CONFIG_ADMIN_AUTH_TOKEN: admin_auth_token, RocketChatType.CONFIG_ADMIN_USER_ID: admin_user_id, RocketChatType.CONFIG_SECRET: secret, } rc_host = urlparse(base_url).netloc self.object = Channel( uuid=uuid4(), org=self.org, channel_type=RocketChatType.code, config=config, name=truncate(f"{RocketChatType.name}: {rc_host}", Channel._meta.get_field("name").max_length), created_by=self.request.user, modified_by=self.request.user, ) client = Client(config[RocketChatType.CONFIG_BASE_URL], config[RocketChatType.CONFIG_SECRET]) webhook_url = "https://" + self.object.callback_domain + reverse( "courier.rc", args=[self.object.uuid]) try: client.settings(webhook_url, bot_username) except ClientError as err: messages.error( self.request, err.msg if err.msg else _("Configuration has failed")) return super().get(self.request, *self.args, **self.kwargs) else: self.request.session.pop(self.SESSION_KEY, None) self.object.save() return super().form_valid(form)
def claim_number(self, user, phone_number, country, role): org = user.get_org() client = org.get_twilio_client() twilio_phones = client.api.incoming_phone_numbers.stream(phone_number=phone_number) channel_uuid = uuid4() # create new TwiML app callback_domain = org.get_brand_domain() twilio_phone = next(twilio_phones, None) if not twilio_phone: raise Exception(_("Only existing Twilio WhatsApp number are supported")) phone = phonenumbers.format_number( phonenumbers.parse(phone_number, None), phonenumbers.PhoneNumberFormat.NATIONAL ) number_sid = twilio_phone.sid org_config = org.config config = { Channel.CONFIG_NUMBER_SID: number_sid, Channel.CONFIG_ACCOUNT_SID: org_config[Org.CONFIG_TWILIO_SID], Channel.CONFIG_AUTH_TOKEN: org_config[Org.CONFIG_TWILIO_TOKEN], Channel.CONFIG_CALLBACK_DOMAIN: callback_domain, } role = Channel.ROLE_SEND + Channel.ROLE_RECEIVE channel = Channel.create( org, user, country, "TWA", name=phone, address=phone_number, role=role, config=config, uuid=channel_uuid, schemes=[WHATSAPP_SCHEME], ) analytics.track(user.username, "temba.channel_claim_twilio_whatsapp", properties=dict(number=phone_number)) return channel
def create_archive(self, org, idx, start_date=None, period="D"): if not start_date: start_date = date(2018, idx, 1) period = "M" archive_hash = uuid4().hex return Archive.objects.create( archive_type=Archive.TYPE_MSG if idx % 2 == 0 else Archive.TYPE_FLOWRUN, size=100_000 * idx, hash=archive_hash, url=f"http://s3-bucket.aws.com/my/{archive_hash}.jsonl.gz", record_count=123_456_789 * idx, start_date=start_date, period=period, build_time=idx * 123, org=org, )
def create(cls, org, user, classifier_type, name, config, sync=True): classifier = Classifier.objects.create( uuid=uuid4(), name=name, classifier_type=classifier_type, config=config, org=org, created_by=user, modified_by=user, created_on=timezone.now(), modified_on=timezone.now(), ) # trigger a sync of this classifier's intents if sync: classifier.async_sync() return classifier
def create_flow(self, name="Test Flow", *, flow_type=Flow.TYPE_MESSAGE, nodes=None, is_system=False, org=None): org = org or self.org flow = Flow.create(org, self.admin, name, flow_type=flow_type, is_system=is_system) if not nodes: nodes = [{ "uuid": "f3d5ccd0-fee0-4955-bcb7-21613f049eae", "actions": [{ "uuid": "f661e3f0-5148-4397-92ef-925629ad444d", "type": "send_msg", "text": "Hey everybody!" }], "exits": [{ "uuid": "72a3f1da-bde1-4549-a986-d35809807be8" }], }] definition = { "uuid": str(uuid4()), "name": name, "type": Flow.GOFLOW_TYPES[flow_type], "revision": 1, "spec_version": "13.1.0", "expire_after_minutes": Flow.DEFAULT_EXPIRES_AFTER, "language": "eng", "nodes": nodes, } flow.version_number = definition["spec_version"] flow.save() json_flow = Flow.migrate_definition(definition, flow) flow.save_revision(self.admin, json_flow) return flow
def form_valid(self, form): org = self.request.user.get_org() data = form.cleaned_data country = data["country"] url = data["url"] number = data["number"] role = Channel.ROLE_SEND + Channel.ROLE_RECEIVE config = { Channel.CONFIG_SEND_URL: url, Channel.CONFIG_VERIFY_SSL: data.get("verify_ssl", False), Channel.CONFIG_USE_NATIONAL: data.get("use_national", False), Channel.CONFIG_USERNAME: data.get("username", None), Channel.CONFIG_PASSWORD: data.get("password", None), Channel.CONFIG_ENCODING: data.get("encoding", Channel.ENCODING_DEFAULT), Channel.CONFIG_CALLBACK_DOMAIN: org.get_brand_domain(), } self.object = Channel.add_config_external_channel(org, self.request.user, country, number, "KN", config, role=role, parent=None) # if they didn't set a username or password, generate them, we do this after the addition above # because we use the channel id in the configuration config = self.object.config if not config.get(Channel.CONFIG_USERNAME, None): config[Channel.CONFIG_USERNAME] = "%s_%d" % ( self.request.branding["name"].lower(), self.object.pk) if not config.get(Channel.CONFIG_PASSWORD, None): config[Channel.CONFIG_PASSWORD] = str(uuid4()) self.object.config = config self.object.save() return super().form_valid(form)
def visit(self, node, exit_index=None): if self.current_node: from_exit = None if exit_index: from_exit = self.current_node["exits"][exit_index] else: # use first exit that points to this destination for e in self.current_node["exits"]: if e.get("destination_uuid") == node["uuid"]: from_exit = e break assert from_exit, f"previous node {self.current_node['uuid']} has no exit to new node {node['uuid']}" self.current_run["path"][-1]["exit_uuid"] = from_exit["uuid"] self.current_run["path"].append({"uuid": str(uuid4()), "node_uuid": node["uuid"], "arrived_on": self._now()}) self.current_run["modified_on"] = self._now() self.current_node = node return self
def populate_session_uuids(apps, schema_editor): # pragma: no cover FlowSession = apps.get_model("flows", "FlowSession") num_updated = 0 max_id = -1 while True: batch = list( FlowSession.objects.filter(uuid=None, id__gt=max_id).only( "id", "uuid").order_by("id")[:BATCH_SIZE]) if not batch: break with transaction.atomic(): for session in batch: session.uuid = str(uuid4()) session.save(update_fields=("uuid", )) num_updated += len(batch) print(f" > Updated {num_updated} flow sessions with a UUID") max_id = batch[-1].id
def test_iter_records(self): archive = Archive.objects.create( org=self.org, archive_type=Archive.TYPE_FLOWRUN, size=10, hash=uuid4().hex, url=f"http://s3-bucket.aws.com/my/32562662.jsonl.gz", record_count=2, start_date=timezone.now(), period="D", build_time=23425, ) mock_s3 = MockS3Client() mock_s3.put_jsonl("s3-bucket", "my/32562662.jsonl.gz", [{"id": 1}, {"id": 2}, {"id": 3}]) with patch("temba.archives.models.Archive.s3_client", return_value=mock_s3): records_iter = archive.iter_records() self.assertEqual(next(records_iter), {"id": 1}) self.assertEqual(next(records_iter), {"id": 2}) self.assertEqual(next(records_iter), {"id": 3}) self.assertRaises(StopIteration, next, records_iter)
def __init__(self, uuid): self.uuid = uuid if uuid else str(uuid4())
def claim_number(self, user, phone_number, country, role): org = user.get_org() client = org.get_twilio_client() twilio_phones = client.api.incoming_phone_numbers.stream( phone_number=phone_number) channel_uuid = uuid4() # create new TwiML app callback_domain = org.get_brand_domain() base_url = "https://" + callback_domain receive_url = base_url + reverse("courier.t", args=[channel_uuid, "receive"]) status_url = base_url + reverse("mailroom.ivr_handler", args=[channel_uuid, "status"]) voice_url = base_url + reverse("mailroom.ivr_handler", args=[channel_uuid, "incoming"]) new_app = client.api.applications.create( friendly_name="%s/%s" % (callback_domain.lower(), channel_uuid), sms_method="POST", sms_url=receive_url, voice_method="POST", voice_url=voice_url, status_callback_method="POST", status_callback=status_url, voice_fallback_method="GET", voice_fallback_url=f"{settings.STORAGE_URL}/voice_unavailable.xml", ) is_short_code = len(phone_number) <= 6 if is_short_code: short_codes = client.api.short_codes.stream( short_code=phone_number) short_code = next(short_codes, None) if short_code: number_sid = short_code.sid app_url = "https://" + callback_domain + "%s" % reverse( "courier.t", args=[channel_uuid, "receive"]) client.api.short_codes.get(number_sid).update( sms_url=app_url, sms_method="POST") role = Channel.ROLE_SEND + Channel.ROLE_RECEIVE phone = phone_number else: # pragma: no cover raise Exception( _("Short code not found on your Twilio Account. " "Please check you own the short code and Try again")) else: twilio_phone = next(twilio_phones, None) if twilio_phone: client.api.incoming_phone_numbers.get(twilio_phone.sid).update( voice_application_sid=new_app.sid, sms_application_sid=new_app.sid) else: # pragma: needs cover twilio_phone = client.api.incoming_phone_numbers.create( phone_number=phone_number, voice_application_sid=new_app.sid, sms_application_sid=new_app.sid) phone = phonenumbers.format_number( phonenumbers.parse(phone_number, None), phonenumbers.PhoneNumberFormat.NATIONAL) number_sid = twilio_phone.sid org_config = org.config config = { Channel.CONFIG_APPLICATION_SID: new_app.sid, Channel.CONFIG_NUMBER_SID: number_sid, Channel.CONFIG_ACCOUNT_SID: org_config[Org.CONFIG_TWILIO_SID], Channel.CONFIG_AUTH_TOKEN: org_config[Org.CONFIG_TWILIO_TOKEN], Channel.CONFIG_CALLBACK_DOMAIN: callback_domain, } channel = Channel.create(org, user, country, "T", name=phone, address=phone_number, role=role, config=config, uuid=channel_uuid) analytics.track(user.username, "temba.channel_claim_twilio", properties=dict(number=phone_number)) return channel
def generate_key(self): unique = uuid4() return hmac.new(unique.bytes, digestmod=sha1).hexdigest()
def generate_uuid(): """ Returns a random stringified UUID for use with older models that use char fields instead of UUID fields """ return str(uuid.uuid4())