Exemple #1
0
    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)
Exemple #2
0
    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)
Exemple #3
0
    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")
Exemple #4
0
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)
Exemple #5
0
 def deactivate(self, channel):
     # deactivate all translations associated with us
     TemplateTranslation.trim(channel, [])
Exemple #6
0
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)}")
Exemple #7
0
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)