Improve outreach compose, contact merge, and email assets.
Add a Quill email editor with DB-backed image storage, selectable PCM designs, postcard defaults for addressed contacts, and merge-by-phone/address on the contact form.
This commit is contained in:
@@ -5,9 +5,11 @@ from django.urls import reverse
|
||||
from contacts.models import Channel, ConsentRecord, Contact, Suppression
|
||||
from messaging.models import Campaign, Message, MessageTemplate, ProviderEvent
|
||||
from messaging.services import (
|
||||
channel_preferences,
|
||||
contact_may_receive,
|
||||
create_campaign_draft,
|
||||
make_unsubscribe_token,
|
||||
opted_in_contacts,
|
||||
set_channel_consent,
|
||||
)
|
||||
|
||||
@@ -757,3 +759,143 @@ class PcmAuthTests(TestCase):
|
||||
with self.assertRaises(pcm_mod.PcmApiError) as ctx:
|
||||
pcm_mod.login(force=True)
|
||||
self.assertIn("PCM_API_SECRET", str(ctx.exception))
|
||||
|
||||
|
||||
class PostcardAddressDefaultConsentTests(TestCase):
|
||||
def test_address_without_consent_is_postcard_eligible(self):
|
||||
contact = Contact.objects.create(
|
||||
email="addr@example.com",
|
||||
first_name="Ann",
|
||||
postal_address=Contact.make_postal_address(
|
||||
line1="1 Oak St",
|
||||
city="Naperville",
|
||||
state="IL",
|
||||
zip_code="60540",
|
||||
),
|
||||
)
|
||||
self.assertTrue(contact_may_receive(contact, Channel.POSTCARD))
|
||||
prefs = channel_preferences(contact)
|
||||
self.assertTrue(prefs[Channel.POSTCARD])
|
||||
self.assertEqual(opted_in_contacts(Channel.POSTCARD).count(), 1)
|
||||
|
||||
def test_explicit_postcard_opt_out_respected(self):
|
||||
contact = Contact.objects.create(
|
||||
email="out@example.com",
|
||||
postal_address=Contact.make_postal_address(line1="2 Oak St"),
|
||||
)
|
||||
set_channel_consent(
|
||||
contact, Channel.POSTCARD, opted_in=False, reason="opt_out"
|
||||
)
|
||||
self.assertFalse(contact_may_receive(contact, Channel.POSTCARD))
|
||||
self.assertEqual(opted_in_contacts(Channel.POSTCARD).count(), 0)
|
||||
|
||||
|
||||
class CampaignHtmlBodyTests(TestCase):
|
||||
def test_html_body_preserved_in_email(self):
|
||||
from django.core import mail
|
||||
|
||||
User = get_user_model()
|
||||
user = User.objects.create_user(
|
||||
username="htmlsender", password="test-pass-123", email="s@example.com"
|
||||
)
|
||||
contact = Contact.objects.create(email="pat@example.com", first_name="Pat")
|
||||
set_channel_consent(contact, Channel.EMAIL, opted_in=True, reason="test")
|
||||
campaign = create_campaign_draft(
|
||||
name="HTML tip",
|
||||
audience=Campaign.Audience.EMAIL_OPT_IN,
|
||||
subject="Styled",
|
||||
body='<p><strong>Hello</strong> <img src="https://example.com/a.png"></p>',
|
||||
created_by=user,
|
||||
)
|
||||
from messaging.providers.email.smtp2go import send_email
|
||||
|
||||
send_email(campaign.messages.get())
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
html = mail.outbox[0].alternatives[0][0]
|
||||
self.assertIn("<strong>Hello</strong>", html)
|
||||
self.assertIn('src="https://example.com/a.png"', html)
|
||||
|
||||
|
||||
class PostcardDesignPickTests(TestCase):
|
||||
def setUp(self):
|
||||
User = get_user_model()
|
||||
self.user = User.objects.create_user(
|
||||
username="pcm-pick", password="test-pass-123"
|
||||
)
|
||||
self.client = Client()
|
||||
self.client.login(username="pcm-pick", password="test-pass-123")
|
||||
self.contact = Contact.objects.create(
|
||||
email="mail@example.com",
|
||||
postal_address=Contact.make_postal_address(line1="9 Main"),
|
||||
)
|
||||
|
||||
def test_compose_can_pick_pcm_design_id(self):
|
||||
from unittest.mock import patch
|
||||
|
||||
with patch(
|
||||
"messaging.views._fetch_pcm_designs",
|
||||
return_value=(
|
||||
[{"design_id": "42", "name": "Spring card", "size": "46"}],
|
||||
"",
|
||||
),
|
||||
):
|
||||
url = reverse("messaging:campaign_list")
|
||||
response = self.client.post(
|
||||
url,
|
||||
{
|
||||
"name": "Mailer",
|
||||
"subject": "",
|
||||
"body": "",
|
||||
"audience": Campaign.Audience.POSTCARD_OPT_IN,
|
||||
"template_id": "d:42",
|
||||
},
|
||||
)
|
||||
campaign = Campaign.objects.get(name="Mailer")
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(campaign.channel, Channel.POSTCARD)
|
||||
self.assertIsNotNone(campaign.template_id)
|
||||
self.assertEqual(
|
||||
campaign.template.postcard_front.get("design_id"), 42
|
||||
)
|
||||
self.assertEqual(campaign.messages.count(), 1)
|
||||
|
||||
|
||||
class StoredFileUploadTests(TestCase):
|
||||
def setUp(self):
|
||||
User = get_user_model()
|
||||
self.user = User.objects.create_user(
|
||||
username="uploader", password="test-pass-123"
|
||||
)
|
||||
self.client = Client()
|
||||
self.client.login(username="uploader", password="test-pass-123")
|
||||
|
||||
def test_upload_stores_bytes_in_database(self):
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
|
||||
from messaging.models import StoredFile
|
||||
|
||||
png = (
|
||||
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01"
|
||||
b"\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00"
|
||||
b"\x00\x0cIDATx\x9cc\xf8\x0f\x00\x00\x01\x01\x00\x05\x18"
|
||||
b"\xd8N\x00\x00\x00\x00IEND\xaeB`\x82"
|
||||
)
|
||||
upload = SimpleUploadedFile("dot.png", png, content_type="image/png")
|
||||
response = self.client.post(
|
||||
reverse("messaging:campaign_image_upload"),
|
||||
{"image": upload},
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
payload = response.json()
|
||||
self.assertIn("url", payload)
|
||||
stored = StoredFile.objects.get()
|
||||
self.assertEqual(bytes(stored.data), png)
|
||||
self.assertEqual(stored.content_type, "image/png")
|
||||
self.assertEqual(stored.uploaded_by, self.user)
|
||||
|
||||
fetch = self.client.get(
|
||||
reverse("messaging:stored_file", kwargs={"pk": stored.pk})
|
||||
)
|
||||
self.assertEqual(fetch.status_code, 200)
|
||||
self.assertEqual(fetch["Content-Type"], "image/png")
|
||||
self.assertEqual(b"".join(fetch.streaming_content), png)
|
||||
|
||||
Reference in New Issue
Block a user