Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
 into MNCH-564-conentrepo-template-error-message

# Conflicts:
#	home/tests/test_models.py
  • Loading branch information
Buhle79 committed Jan 23, 2024
2 parents b94b4b9 + 0987b0f commit e1165e5
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 7 deletions.
4 changes: 0 additions & 4 deletions contentrepo/settings/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@

EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

WHATSAPP_API_URL = "http://whatsapp"
WHATSAPP_ACCESS_TOKEN = "fake-access-token" # noqa: S105 (This is a test config.)
FB_BUSINESS_ID = "27121231234"


# We default to using local memory for dev and tests, so that Redis isn't a dependancy
# We test against Redis in the CI
Expand Down
6 changes: 6 additions & 0 deletions contentrepo/settings/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@

DATABASES = {"default": env.db("CONTENTREPO_DATABASE", default="sqlite://:memory:")}
PASSWORD_HASHERS = ("django.contrib.auth.hashers.MD5PasswordHasher",)

WHATSAPP_API_URL = "http://whatsapp"
WHATSAPP_ACCESS_TOKEN = "fake-access-token" # noqa: S105 (This is a test config.)
FB_BUSINESS_ID = "27121231234"

WHATSAPP_CREATE_TEMPLATES = False
25 changes: 25 additions & 0 deletions home/migrations/0041_alter_contentpage_whatsapp_body.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 4.2.9 on 2024-01-10 09:41

import django.core.validators
import wagtail.blocks
import wagtail.documents.blocks
import wagtail.fields
import wagtail.images.blocks
from django.db import migrations

import home.models


class Migration(migrations.Migration):

dependencies = [
('home', '0040_alter_contentpage_whatsapp_template_category'),
]

operations = [
migrations.AlterField(
model_name='contentpage',
name='whatsapp_body',
field=wagtail.fields.StreamField([('Whatsapp_Message', wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(required=False)), ('document', wagtail.documents.blocks.DocumentChooserBlock(icon='document', required=False)), ('media', home.models.MediaBlock(icon='media', required=False)), ('message', wagtail.blocks.TextBlock(help_text='each text message cannot exceed 4096 characters, messages with media cannot exceed 1024 characters.', validators=(django.core.validators.MaxLengthValidator(4096), django.core.validators.RegexValidator(code='invalid_variables', inverse_match=True, message='Placeholders are not digits or not in consecutive order starting with {{1}} ', regex='{{\\s*\\w+\\s*}}')))), ('example_values', wagtail.blocks.ListBlock(wagtail.blocks.CharBlock(label='Example Value'), default=[], help_text='Please add example values for all variables used in a WhatsApp template', label='Variable Example Values')), ('variation_messages', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('variation_restrictions', wagtail.blocks.StreamBlock([('gender', wagtail.blocks.ChoiceBlock(choices=home.models.get_gender_choices)), ('age', wagtail.blocks.ChoiceBlock(choices=home.models.get_age_choices)), ('relationship', wagtail.blocks.ChoiceBlock(choices=home.models.get_relationship_choices))], help_text='Restrict this variation to users with this profile value. Valid values must be added to the Site Settings', max_num=1, min_num=1, required=False, use_json_field=True)), ('message', wagtail.blocks.TextBlock(help_text='each message cannot exceed 4096 characters.', validators=(django.core.validators.MaxLengthValidator(4096),)))]), default=[])), ('next_prompt', wagtail.blocks.CharBlock(help_text='prompt text for next message', required=False, validators=(django.core.validators.MaxLengthValidator(20),))), ('buttons', wagtail.blocks.StreamBlock([('next_message', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='text for the button, up to 20 characters.', validators=(django.core.validators.MaxLengthValidator(20),)))])), ('go_to_page', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(help_text='text for the button, up to 20 characters.', validators=(django.core.validators.MaxLengthValidator(20),))), ('page', wagtail.blocks.PageChooserBlock(help_text='page the button should go to'))]))], max_num=3, required=False))], help_text='Each message will be sent with the text and media'))], blank=True, null=True, use_json_field=True),
),
]
12 changes: 12 additions & 0 deletions home/migrations/0045_merge_20240123_1102.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Generated by Django 4.2.9 on 2024-01-23 11:02

from django.db import migrations


class Migration(migrations.Migration):
dependencies = [
("home", "0041_alter_contentpage_whatsapp_body"),
("home", "0044_merge_20240118_1014"),
]

operations = []
60 changes: 58 additions & 2 deletions home/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from taggit.models import ItemBase, TagBase, TaggedItemBase
from wagtail import blocks
from wagtail.api import APIField
from wagtail.blocks import StructBlockValidationError
from wagtail.blocks import StreamBlockValidationError, StructBlockValidationError
from wagtail.contrib.settings.models import BaseSiteSetting, register_setting
from wagtail.documents.blocks import DocumentChooserBlock
from wagtail.fields import StreamField
Expand Down Expand Up @@ -238,7 +238,6 @@ class GoToPageButton(blocks.StructBlock):

class WhatsappBlock(blocks.StructBlock):
MEDIA_CAPTION_MAX_LENGTH = 1024

image = ImageChooserBlock(required=False)
document = DocumentChooserBlock(icon="document", required=False)
media = MediaBlock(icon="media", required=False)
Expand All @@ -247,6 +246,7 @@ class WhatsappBlock(blocks.StructBlock):
"media cannot exceed 1024 characters.",
validators=(MaxLengthValidator(4096),),
)

example_values = blocks.ListBlock(
blocks.CharBlock(
label="Example Value",
Expand Down Expand Up @@ -298,6 +298,7 @@ def clean(self, value):
f"{self.MEDIA_CAPTION_MAX_LENGTH} characters, your message is "
f"{len(result['message'])} characters long"
)

if errors:
raise StructBlockValidationError(errors)
return result
Expand Down Expand Up @@ -853,6 +854,61 @@ def save_revision(
revision.save(update_fields=["content"])
return revision

def clean(self):
result = super().clean()
errors = {}

# The WA title is needed for all templates to generate a name for the template
if self.is_whatsapp_template and not self.whatsapp_title:
errors.setdefault("whatsapp_title", []).append(
ValidationError("All WhatsApp templates need a title.")
)
# The variable check is only for templates
if self.is_whatsapp_template and len(self.whatsapp_body.raw_data) > 0:
whatsapp_message = self.whatsapp_body.raw_data[0]["value"]["message"]
vars_in_msg = re.findall(r"{{(.*?)}}", whatsapp_message)
non_digit_variables = [var for var in vars_in_msg if not var.isdecimal()]

if non_digit_variables:
errors.setdefault("whatsapp_body", []).append(
StreamBlockValidationError(
{
0: StreamBlockValidationError(
{
"message": ValidationError(
f"Please provide numeric variables only. You provided {non_digit_variables}."
)
}
)
}
)
)

# Check variable order
actual_digit_variables = [var for var in vars_in_msg if var.isdecimal()]
expected_variables = [
str(j + 1) for j in range(len(actual_digit_variables))
]
if actual_digit_variables != expected_variables:
errors.setdefault("whatsapp_body", []).append(
StreamBlockValidationError(
{
0: StreamBlockValidationError(
{
"message": ValidationError(
f'Variables must be sequential, starting with "{{1}}". Your first variable was "{actual_digit_variables}"'
)
}
)
}
)
)

if errors:
raise ValidationError(errors)

return result


# Allow slug to be blank in forms, we fill it in in full_clean
Page._meta.get_field("slug").blank = True
Expand Down
48 changes: 47 additions & 1 deletion home/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,16 +229,62 @@ def test_template_not_submitted_with_no_message(
"""
home_page = HomePage.objects.first()
main_menu = PageBuilder.build_cpi(home_page, "main-menu", "Main Menu")

PageBuilder.build_cp(
parent=main_menu,
slug="ha-menu",
title="HealthAlert menu",
bodies=[],
bodies=[WABody("WA Title", [])],
whatsapp_template_name="WA_Title_1",
)

mock_create_whatsapp_template.assert_not_called()

@override_settings(WHATSAPP_CREATE_TEMPLATES=True)
@mock.patch("home.models.create_whatsapp_template")
def test_template_submitted_with_no_title(self, mock_create_whatsapp_template):
"""
If the page is a WA template and how no title, then it shouldn't be submitted
"""

with self.assertRaises(ValidationError):
home_page = HomePage.objects.first()
main_menu = PageBuilder.build_cpi(home_page, "main-menu", "Main Menu")

PageBuilder.build_cp(
parent=main_menu,
slug="template-no-title",
title="HealthAlert menu",
bodies=[WABody("", [])],
whatsapp_template_name="WA_Title_1",
)

mock_create_whatsapp_template.assert_not_called()

def test_clean_text_valid_variables(self):
"""
The message should accept variables if and only if they are numeric and ordered
"""
home_page = HomePage.objects.first()
main_menu = PageBuilder.build_cpi(home_page, "main-menu", "Main Menu")
with self.assertRaises(ValidationError):
PageBuilder.build_cp(
parent=main_menu,
slug="ha-menu",
title="HealthAlert menu",
bodies=[
WABody(
"WA Title",
[
WABlk(
"{{2}}{{1}} {{foo}}",
)
],
)
],
whatsapp_template_name="WA_Title_1",
)

@override_settings(WHATSAPP_CREATE_TEMPLATES=True)
@mock.patch("home.models.create_whatsapp_template")
def test_create_whatsapp_template_submit_no_error_message(
Expand Down
51 changes: 51 additions & 0 deletions requirements-lock.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
anyascii==0.3.2
asgiref==3.7.2
attrs==23.2.0
beautifulsoup4==4.11.2
certifi==2023.11.17
charset-normalizer==3.3.2
defusedxml==0.7.1
dj-database-url==2.1.0
Django==4.2.9
django-environ==0.11.2
django-filter==23.5
django-modelcluster==6.2.1
django-permissionedforms==0.1
django-taggit==4.0.0
django-treebeard==4.7
djangorestframework==3.14.0
draftjs-exporter==2.1.7
drf-spectacular==0.27.0
environ==1.0
et-xmlfile==1.1.0
filetype==1.2.0
html5lib==1.1
idna==3.6
inflection==0.5.1
jsonschema==4.20.0
jsonschema-specifications==2023.12.1
l18n==2021.3
lxml==5.1.0
openpyxl==3.1.2
pillow==10.2.0
pillow_heif==0.14.0
psycopg==3.1.17
psycopg2==2.9.9
python-docx==1.1.0
pytz==2023.3.post1
PyYAML==6.0.1
referencing==0.32.1
requests==2.31.0
rpds-py==0.16.2
six==1.16.0
soupsieve==2.5
sqlparse==0.4.4
telepath==0.3.1
typing_extensions==4.9.0
uritemplate==4.1.1
urllib3==2.1.0
wagtail==5.2.2
wagtail-content-import==0.11.0
wagtailmedia==0.14.5
webencodings==0.5.1
Willow==1.6.3

0 comments on commit e1165e5

Please sign in to comment.