def test_message_templates_and_logs_views(self): channel = self.create_channel("WA", "Channel", "1234", config={"fb_namespace": "foo_namespace"}) TemplateTranslation.get_or_create( channel, "hello", "eng", "US", "Hello {{1}}", 1, TemplateTranslation.STATUS_APPROVED, "1234", "foo_namespace", ) self.login(self.admin) # hit our template page response = self.client.get(reverse("channels.types.whatsapp.templates", args=[channel.uuid])) # should have our template translations self.assertContains(response, "Hello") self.assertContains(response, reverse("channels.types.whatsapp.sync_logs", args=[channel.uuid])) # Check if message templates link are in sync_logs view response = self.client.get(reverse("channels.types.whatsapp.sync_logs", args=[channel.uuid])) gear_links = response.context["view"].get_gear_links() self.assertEqual(gear_links[-1]["title"], "Message Templates") self.assertEqual(gear_links[-1]["href"], reverse("channels.types.whatsapp.templates", args=[channel.uuid])) # sync logs and message templates not accessible by user from other org self.login(self.admin2) response = self.client.get(reverse("channels.types.whatsapp.templates", args=[channel.uuid])) self.assertEqual(404, response.status_code) response = self.client.get(reverse("channels.types.whatsapp.sync_logs", args=[channel.uuid])) self.assertEqual(404, response.status_code)
def test_message_templates_and_logs_views(self): channel = self.create_channel( "D3", "360Dialog channel", address="1234", country="BR", config={ Channel.CONFIG_BASE_URL: "https://example.com/whatsapp", Channel.CONFIG_AUTH_TOKEN: "123456789", }, ) TemplateTranslation.get_or_create( channel, "hello", "eng", "US", "Hello {{1}}", 1, TemplateTranslation.STATUS_APPROVED, "1234", "foo_namespace", ) self.login(self.admin) # hit our template page response = self.client.get( reverse("channels.types.dialog360.templates", args=[channel.uuid])) # should have our template translations self.assertContains(response, "Hello") self.assertContains( response, reverse("channels.types.dialog360.sync_logs", args=[channel.uuid])) # Check if message templates link are in sync_logs view response = self.client.get( reverse("channels.types.dialog360.sync_logs", args=[channel.uuid])) gear_links = response.context["view"].get_gear_links() self.assertEqual(gear_links[-1]["title"], "Message Templates") self.assertEqual( gear_links[-1]["href"], reverse("channels.types.dialog360.templates", args=[channel.uuid])) # sync logs not accessible by user from other org self.login(self.admin2) response = self.client.get( reverse("channels.types.dialog360.templates", args=[channel.uuid])) self.assertEqual(404, response.status_code) response = self.client.get( reverse("channels.types.dialog360.sync_logs", args=[channel.uuid])) self.assertEqual(404, response.status_code)
def create_templates(self, spec, org, templates): self._log(f"Creating {len(spec['templates'])} templates... ") for t in spec["templates"]: Template.objects.create(org=org, uuid=t["uuid"], name=t["name"]) for tt in t["translations"]: channel = Channel.objects.get(uuid=tt["channel_uuid"]) TemplateTranslation.get_or_create( channel, t["name"], tt["language"], tt["content"], tt["variable_count"], tt["status"], tt["external_id"], ) self._log(self.style.SUCCESS("OK") + "\n")
def refresh_whatsapp_templates(): """ Runs across all WhatsApp templates that have connected FB accounts and syncs the templates which are active. """ from .type import ( CONFIG_FB_BUSINESS_ID, CONFIG_FB_ACCESS_TOKEN, CONFIG_FB_TEMPLATE_LIST_DOMAIN, LANGUAGE_MAPPING, STATUS_MAPPING, TEMPLATE_LIST_URL, ) r = get_redis_connection() if r.get("refresh_whatsapp_templates"): # pragma: no cover return with r.lock("refresh_whatsapp_templates", 1800): # for every whatsapp channel for channel in Channel.objects.filter(is_active=True, channel_type="WA"): # move on if we have no FB credentials if (CONFIG_FB_BUSINESS_ID not in channel.config or CONFIG_FB_ACCESS_TOKEN not in channel.config): # pragma: no cover continue # fetch all our templates start = timezone.now() try: # Retrieve the template domain, fallback to the default for channels # that have been setup earlier for backwards compatibility facebook_template_domain = channel.config.get( CONFIG_FB_TEMPLATE_LIST_DOMAIN, "graph.facebook.com") facebook_business_id = channel.config.get( CONFIG_FB_BUSINESS_ID) url = TEMPLATE_LIST_URL % (facebook_template_domain, facebook_business_id) # we should never need to paginate because facebook limits accounts to 255 templates response = requests.get( url, params=dict( access_token=channel.config[CONFIG_FB_ACCESS_TOKEN], limit=255)) elapsed = (timezone.now() - start).total_seconds() * 1000 HTTPLog.create_from_response(HTTPLog.WHATSAPP_TEMPLATES_SYNCED, url, response, channel=channel, request_time=elapsed) if response.status_code != 200: # pragma: no cover continue # run through all our templates making sure they are present in our DB seen = [] for template in response.json()["data"]: # if this is a status we don't know about if template["status"] not in STATUS_MAPPING: logger.error( f"unknown whatsapp status: {template['status']}") continue status = STATUS_MAPPING[template["status"]] content_parts = [] all_supported = True for component in template["components"]: if component["type"] not in [ "HEADER", "BODY", "FOOTER" ]: logger.error( f"unknown component type: {component}") continue if component["type"] in [ "HEADER", "FOOTER" ] and _calculate_variable_count(component["text"]): logger.error( f"unsupported component type wih variables: {component}" ) all_supported = False content_parts.append(component["text"]) if not content_parts or not all_supported: continue content = "\n\n".join(content_parts) variable_count = _calculate_variable_count(content) language = LANGUAGE_MAPPING.get(template["language"]) # its a (non fatal) error if we see a language we don't know if language is None: status = TemplateTranslation.STATUS_UNSUPPORTED_LANGUAGE language = template["language"] translation = TemplateTranslation.get_or_create( channel=channel, name=template["name"], language=language, content=content, variable_count=variable_count, status=status, external_id=template["id"], ) seen.append(translation) # trim any translations we didn't see TemplateTranslation.trim(channel, seen) except RequestException as e: # pragma: no cover HTTPLog.create_from_exception( HTTPLog.WHATSAPP_TEMPLATES_SYNCED, url, e, start, channel=channel)
def refresh_whatsapp_templates(): """ Runs across all WhatsApp templates that have connected FB accounts and syncs the templates which are active. """ from .type import ( CONFIG_FB_BUSINESS_ID, CONFIG_FB_ACCESS_TOKEN, CONFIG_FB_TEMPLATE_LIST_DOMAIN, LANGUAGE_MAPPING, STATUS_MAPPING, TEMPLATE_LIST_URL, ) r = get_redis_connection() if r.get("refresh_whatsapp_templates"): # pragma: no cover return with r.lock("refresh_whatsapp_templates", 1800): # for every whatsapp channel for channel in Channel.objects.filter(is_active=True, channel_type="WA"): # move on if we have no FB credentials if (CONFIG_FB_BUSINESS_ID not in channel.config or CONFIG_FB_ACCESS_TOKEN not in channel.config): # pragma: no cover continue # fetch all our templates try: # Retrieve the template domain, fallback to the default for channels # that have been setup earlier for backwards compatibility facebook_template_domain = channel.config.get( CONFIG_FB_TEMPLATE_LIST_DOMAIN, "graph.facebook.com") facebook_business_id = channel.config.get( CONFIG_FB_BUSINESS_ID) # we should never need to paginate because facebook limits accounts to 255 templates response = requests.get( TEMPLATE_LIST_URL % (facebook_template_domain, facebook_business_id), params=dict( access_token=channel.config[CONFIG_FB_ACCESS_TOKEN], limit=255), ) if response.status_code != 200: # pragma: no cover raise Exception( f"received non 200 status: {response.status_code} {response.content}" ) # run through all our templates making sure they are present in our DB seen = [] for template in response.json()["data"]: # its a (non fatal) error if we see a language we don't know if template["language"] not in LANGUAGE_MAPPING: logger.error( f"unknown whatsapp language: {template['language']}" ) continue # or if this is a status we don't know about if template["status"] not in STATUS_MAPPING: logger.error( f"unknown whatsapp status: {template['status']}") continue # try to get the body out if template["components"][0][ "type"] != "BODY": # pragma: no cover logger.error( f"unknown component type: {template['components'][0]}" ) continue content = template["components"][0]["text"] translation = TemplateTranslation.get_or_create( channel=channel, name=template["name"], language=LANGUAGE_MAPPING[template["language"]], content=content, variable_count=_calculate_variable_count(content), status=STATUS_MAPPING[template["status"]], external_id=template["id"], ) seen.append(translation) # trim any translations we didn't see TemplateTranslation.trim(channel, seen) except Exception as e: # pragma: no cover logger.error( f"error fetching templates for whatsapp channel: {str(e)}")
def update_local_templates(channel, templates_data): channel_namespace = channel.config.get("fb_namespace", "") # run through all our templates making sure they are present in our DB seen = [] for template in templates_data: template_status = template["status"] template_status = template_status.upper() # if this is a status we don't know about if template_status not in STATUS_MAPPING: continue status = STATUS_MAPPING[template_status] content_parts = [] all_supported = True for component in template["components"]: if component["type"] not in ["HEADER", "BODY", "FOOTER"]: continue if "text" not in component: continue if component["type"] in [ "HEADER", "FOOTER" ] and _calculate_variable_count(component["text"]): all_supported = False content_parts.append(component["text"]) if not content_parts or not all_supported: continue content = "\n\n".join(content_parts) variable_count = _calculate_variable_count(content) language, country = LANGUAGE_MAPPING.get(template["language"], (None, None)) # its a (non fatal) error if we see a language we don't know if language is None: status = TemplateTranslation.STATUS_UNSUPPORTED_LANGUAGE language = template["language"] missing_external_id = f"{template['language']}/{template['name']}" translation = TemplateTranslation.get_or_create( channel=channel, name=template["name"], language=language, country=country, content=content, variable_count=variable_count, status=status, external_id=template.get("id", missing_external_id), namespace=template.get("namespace", channel_namespace), ) seen.append(translation) # trim any translations we didn't see TemplateTranslation.trim(channel, seen)