From 019e9a423c3948bf34e2ac8aaf9e074965703024 Mon Sep 17 00:00:00 2001 From: Rhenan Bartels Date: Thu, 8 Apr 2021 16:03:21 -0300 Subject: [PATCH 01/13] =?UTF-8?q?Cria=20fun=C3=A7=C3=A3o=20dedica=20a=20en?= =?UTF-8?q?viar=20emails?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- brasilio/test_settings.py | 1 + core/email.py | 12 ++++++++++++ core/tests/test_email.py | 27 +++++++++++++++++++++++++++ core/views.py | 4 ++-- 4 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 core/email.py create mode 100644 core/tests/test_email.py diff --git a/brasilio/test_settings.py b/brasilio/test_settings.py index 208b9f2b..98ab5fd5 100644 --- a/brasilio/test_settings.py +++ b/brasilio/test_settings.py @@ -14,3 +14,4 @@ TEMPLATES[0]["OPTIONS"]["string_if_invalid"] = TEMPLATE_STRING_IF_INVALID # noqa ENABLE_API_AUTH = True DISABLE_RECAPTCHA = False +EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" diff --git a/core/email.py b/core/email.py new file mode 100644 index 00000000..8dd793a7 --- /dev/null +++ b/core/email.py @@ -0,0 +1,12 @@ +from django.core.mail import EmailMessage + + +def send_email(subject, body, from_email, to, **kwargs): + email = EmailMessage( + subject=subject, + body=body, + from_email=from_email, + to=to, + **kwargs + ) + email.send() diff --git a/core/tests/test_email.py b/core/tests/test_email.py new file mode 100644 index 00000000..ac3f48f4 --- /dev/null +++ b/core/tests/test_email.py @@ -0,0 +1,27 @@ +from django.core import mail +from django.test import TestCase + +from core.email import send_email + + +class TestSendEmail(TestCase): + def test_send_emnail(self): + subject = "Subject" + body = "Body" + from_email = "from@example.com" + to = ["to@example.com"] + reply_to = ["Reply To ', to=[settings.DEFAULT_FROM_EMAIL], reply_to=[f'{data["name"]} <{data["email"]}>'], ) - email.send() return redirect(reverse("core:contact") + "?sent=true") else: From b3fe60ca5cba4d80f80f114ffcdc22b2daf02fb2 Mon Sep 17 00:00:00 2001 From: Rhenan Bartels Date: Thu, 8 Apr 2021 16:24:14 -0300 Subject: [PATCH 02/13] Inicia script de envio de emails --- brasilio_auth/management/commands/__init__.py | 0 .../management/commands/send_bulk_emails.py | 31 ++++++++++++++ brasilio_auth/tests/test_commands.py | 40 +++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 brasilio_auth/management/commands/__init__.py create mode 100644 brasilio_auth/management/commands/send_bulk_emails.py create mode 100644 brasilio_auth/tests/test_commands.py diff --git a/brasilio_auth/management/commands/__init__.py b/brasilio_auth/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/brasilio_auth/management/commands/send_bulk_emails.py b/brasilio_auth/management/commands/send_bulk_emails.py new file mode 100644 index 00000000..f5e5979d --- /dev/null +++ b/brasilio_auth/management/commands/send_bulk_emails.py @@ -0,0 +1,31 @@ +import rows +from django.core.management.base import BaseCommand +from django.template import Context, Template + +from core.email import send_email + + +class Command(BaseCommand): + def load_email_template(self, email_template): + with open(email_template, "r") as fobj: + return Template(fobj.read()) + + def add_arguments(self, parser): + parser.add_argument("input_filename") + parser.add_argument("email_template") + parser.add_argument("-d", "--dry-run", default=False, action="store_true") + + def handle(self, *args, **kwargs): + input_filename = kwargs["input_filename"] + table = rows.import_from_csv(input_filename) + template_obj = self.load_email_template(kwargs["email_template"]) + + for row in table: + context = Context(row._asdict()) + rendered_template = template_obj.render(context=context) + send_email( + subject="Subject", + body=rendered_template, + from_email="from@example.com", + to=["to@example.com"], + ) diff --git a/brasilio_auth/tests/test_commands.py b/brasilio_auth/tests/test_commands.py new file mode 100644 index 00000000..b20a9a47 --- /dev/null +++ b/brasilio_auth/tests/test_commands.py @@ -0,0 +1,40 @@ +from tempfile import NamedTemporaryFile + +from django.core import mail +from django.core.management import call_command +from django.test import TestCase + + +class TestSendBulkEmails(TestCase): + def setUp(self): + self.input_file = NamedTemporaryFile(suffix=".csv") + self.email_template = NamedTemporaryFile(suffix=".txt") + + with open(self.input_file.name, "w") as fobj: + fobj.write("nome,data\nnome_1,data_1\nnome_2,data_2") + + with open(self.email_template.name, "w") as fobj: + fobj.write("Enviado para {{ nome }} em {{ data }}") + + self.input_file.seek(0) + self.email_template.seek(0) + + self.expexted_send_email = [ + { + "body": "Enviado para nome_1 em data_1", + }, + { + "body": "Enviado para nome_2 em data_2", + } + ] + + def assert_sent_email_metadata(self, email, metadata): + for key, value in metadata.items(): + assert email.__getattribute__(key) == value + + def test_send_bulk_emails(self): + call_command("send_bulk_emails", self.input_file.name, self.email_template.name) + + assert len(mail.outbox) == 2 + self.assert_sent_email_metadata(mail.outbox[0], self.expexted_send_email[0]) + self.assert_sent_email_metadata(mail.outbox[1], self.expexted_send_email[1]) From 95f239e0cb74fbbfea39a50eaae1378e5f8a826a Mon Sep 17 00:00:00 2001 From: Rhenan Bartels Date: Thu, 8 Apr 2021 16:29:35 -0300 Subject: [PATCH 03/13] =?UTF-8?q?Adiciona=20op=C3=A7=C3=A3o=20dry-run=20no?= =?UTF-8?q?=20script?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../management/commands/send_bulk_emails.py | 15 +++++++++------ brasilio_auth/tests/test_commands.py | 10 ++++++++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/brasilio_auth/management/commands/send_bulk_emails.py b/brasilio_auth/management/commands/send_bulk_emails.py index f5e5979d..8a3a5784 100644 --- a/brasilio_auth/management/commands/send_bulk_emails.py +++ b/brasilio_auth/management/commands/send_bulk_emails.py @@ -23,9 +23,12 @@ def handle(self, *args, **kwargs): for row in table: context = Context(row._asdict()) rendered_template = template_obj.render(context=context) - send_email( - subject="Subject", - body=rendered_template, - from_email="from@example.com", - to=["to@example.com"], - ) + if not kwargs["dry_run"]: + send_email( + subject="Subject", + body=rendered_template, + from_email="from@example.com", + to=["to@example.com"], + ) + else: + print(rendered_template) diff --git a/brasilio_auth/tests/test_commands.py b/brasilio_auth/tests/test_commands.py index b20a9a47..f65d33fd 100644 --- a/brasilio_auth/tests/test_commands.py +++ b/brasilio_auth/tests/test_commands.py @@ -38,3 +38,13 @@ def test_send_bulk_emails(self): assert len(mail.outbox) == 2 self.assert_sent_email_metadata(mail.outbox[0], self.expexted_send_email[0]) self.assert_sent_email_metadata(mail.outbox[1], self.expexted_send_email[1]) + + def test_do_not_send_mail(self): + call_command( + "send_bulk_emails", + self.input_file.name, + self.email_template.name, + "--dry-run" + ) + + assert len(mail.outbox) == 0 From 5dd365c4160a935dac35de5a11679a2a692c9a71 Mon Sep 17 00:00:00 2001 From: Rhenan Bartels Date: Thu, 8 Apr 2021 16:37:19 -0300 Subject: [PATCH 04/13] =?UTF-8?q?Envia=20email=20para=20fila=20ass=C3=ADnc?= =?UTF-8?q?rona?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- brasilio_auth/management/commands/send_bulk_emails.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/brasilio_auth/management/commands/send_bulk_emails.py b/brasilio_auth/management/commands/send_bulk_emails.py index 8a3a5784..0c1b9968 100644 --- a/brasilio_auth/management/commands/send_bulk_emails.py +++ b/brasilio_auth/management/commands/send_bulk_emails.py @@ -1,3 +1,4 @@ +import django_rq import rows from django.core.management.base import BaseCommand from django.template import Context, Template @@ -24,7 +25,8 @@ def handle(self, *args, **kwargs): context = Context(row._asdict()) rendered_template = template_obj.render(context=context) if not kwargs["dry_run"]: - send_email( + django_rq.enqueue( + send_email, subject="Subject", body=rendered_template, from_email="from@example.com", From 39e92eed059969655317b2ece1f89fe2bfa40b8d Mon Sep 17 00:00:00 2001 From: Rhenan Bartels Date: Sat, 10 Apr 2021 13:25:40 -0300 Subject: [PATCH 05/13] Aprimora script de envio de e-mails --- .../management/commands/send_bulk_emails.py | 12 ++++-- brasilio_auth/tests/test_commands.py | 37 ++++++++++++++++++- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/brasilio_auth/management/commands/send_bulk_emails.py b/brasilio_auth/management/commands/send_bulk_emails.py index 0c1b9968..a0fe7536 100644 --- a/brasilio_auth/management/commands/send_bulk_emails.py +++ b/brasilio_auth/management/commands/send_bulk_emails.py @@ -1,5 +1,6 @@ import django_rq import rows +from django.conf import settings from django.core.management.base import BaseCommand from django.template import Context, Template @@ -14,12 +15,17 @@ def load_email_template(self, email_template): def add_arguments(self, parser): parser.add_argument("input_filename") parser.add_argument("email_template") + parser.add_argument("--from") parser.add_argument("-d", "--dry-run", default=False, action="store_true") def handle(self, *args, **kwargs): input_filename = kwargs["input_filename"] table = rows.import_from_csv(input_filename) + error_msg = "Arquivo CSV deve conter campos 'to_email' e 'subject'" + assert {"to_email", "subject"}.issubset(set(table.field_names)), error_msg + template_obj = self.load_email_template(kwargs["email_template"]) + from_email = kwargs["from"] or settings.DEFAULT_FROM_EMAIL for row in table: context = Context(row._asdict()) @@ -27,10 +33,10 @@ def handle(self, *args, **kwargs): if not kwargs["dry_run"]: django_rq.enqueue( send_email, - subject="Subject", + subject=row.subject, body=rendered_template, - from_email="from@example.com", - to=["to@example.com"], + from_email=from_email, + to=[row.to_email], ) else: print(rendered_template) diff --git a/brasilio_auth/tests/test_commands.py b/brasilio_auth/tests/test_commands.py index f65d33fd..fd568ead 100644 --- a/brasilio_auth/tests/test_commands.py +++ b/brasilio_auth/tests/test_commands.py @@ -1,5 +1,7 @@ from tempfile import NamedTemporaryFile +import pytest +from django.conf import settings from django.core import mail from django.core.management import call_command from django.test import TestCase @@ -11,7 +13,10 @@ def setUp(self): self.email_template = NamedTemporaryFile(suffix=".txt") with open(self.input_file.name, "w") as fobj: - fobj.write("nome,data\nnome_1,data_1\nnome_2,data_2") + fobj.write( + "nome,data,to_email,subject\nnome_1,data_1,email_1,subject_1\n" + "nome_2,data_2,email_2,subject_2" + ) with open(self.email_template.name, "w") as fobj: fobj.write("Enviado para {{ nome }} em {{ data }}") @@ -22,9 +27,15 @@ def setUp(self): self.expexted_send_email = [ { "body": "Enviado para nome_1 em data_1", + "subject": "subject_1", + "to": ["email_1"], + "from_email": settings.DEFAULT_FROM_EMAIL, }, { "body": "Enviado para nome_2 em data_2", + "subject": "subject_2", + "to": ["email_2"], + "from_email": settings.DEFAULT_FROM_EMAIL, } ] @@ -39,6 +50,30 @@ def test_send_bulk_emails(self): self.assert_sent_email_metadata(mail.outbox[0], self.expexted_send_email[0]) self.assert_sent_email_metadata(mail.outbox[1], self.expexted_send_email[1]) + def test_send_email_custom_from_email(self): + kwargs = {"from": "Example Email "} + call_command( + "send_bulk_emails", + self.input_file.name, + self.email_template.name, + **kwargs + ) + + self.expexted_send_email[0]["from_email"] = kwargs["from"] + self.expexted_send_email[1]["from_email"] = kwargs["from"] + + assert len(mail.outbox) == 2 + self.assert_sent_email_metadata(mail.outbox[0], self.expexted_send_email[0]) + self.assert_sent_email_metadata(mail.outbox[1], self.expexted_send_email[1]) + + def test_assert_mandatory_fields(self): + with open(self.input_file.name, "w") as fobj: + fobj.write("nome,data\nnome_1,data_1\nnome_2,data_2") + + self.input_file.seek(0) + with pytest.raises(AssertionError): + call_command("send_bulk_emails", self.input_file.name, self.email_template.name) + def test_do_not_send_mail(self): call_command( "send_bulk_emails", From 96121af3a02df70a235fcab815b90bb76fc68f65 Mon Sep 17 00:00:00 2001 From: Rhenan Bartels Date: Sat, 10 Apr 2021 13:33:35 -0300 Subject: [PATCH 06/13] =?UTF-8?q?Print=20dicion=C3=A1rio=20com=20metadados?= =?UTF-8?q?=20de=20emails?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../management/commands/send_bulk_emails.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/brasilio_auth/management/commands/send_bulk_emails.py b/brasilio_auth/management/commands/send_bulk_emails.py index a0fe7536..74f6041f 100644 --- a/brasilio_auth/management/commands/send_bulk_emails.py +++ b/brasilio_auth/management/commands/send_bulk_emails.py @@ -12,6 +12,12 @@ def load_email_template(self, email_template): with open(email_template, "r") as fobj: return Template(fobj.read()) + def print_email_metadata(self, metadata): + for key, value in metadata.items(): + print(f"{key}: {value}") + + print("-" * 80) + def add_arguments(self, parser): parser.add_argument("input_filename") parser.add_argument("email_template") @@ -30,13 +36,16 @@ def handle(self, *args, **kwargs): for row in table: context = Context(row._asdict()) rendered_template = template_obj.render(context=context) + email_kwargs = { + "subject": row.subject, + "body": rendered_template, + "from_email": from_email, + "to": [row.to_email], + } if not kwargs["dry_run"]: django_rq.enqueue( send_email, - subject=row.subject, - body=rendered_template, - from_email=from_email, - to=[row.to_email], + **email_kwargs ) else: - print(rendered_template) + self.print_email_metadata(email_kwargs) From b99b02a2ada7f754082c574214bb7edf87aa4892 Mon Sep 17 00:00:00 2001 From: Rhenan Bartels Date: Sat, 10 Apr 2021 13:41:00 -0300 Subject: [PATCH 07/13] Make lint --- brasilio/worker.py | 5 ++--- .../management/commands/send_bulk_emails.py | 5 +---- brasilio_auth/tests/test_commands.py | 19 ++++--------------- core/email.py | 8 +------- core/tests/test_email.py | 6 +----- core/views.py | 3 +-- covid19/models.py | 2 +- 7 files changed, 11 insertions(+), 37 deletions(-) diff --git a/brasilio/worker.py b/brasilio/worker.py index 995b8a9c..ca82fa29 100644 --- a/brasilio/worker.py +++ b/brasilio/worker.py @@ -1,8 +1,7 @@ -from rq import Worker -from rq.contrib.sentry import register_sentry - from raven import Client from raven.transport import HTTPTransport +from rq import Worker +from rq.contrib.sentry import register_sentry class SentryAwareWorker(Worker): diff --git a/brasilio_auth/management/commands/send_bulk_emails.py b/brasilio_auth/management/commands/send_bulk_emails.py index 74f6041f..38cacc61 100644 --- a/brasilio_auth/management/commands/send_bulk_emails.py +++ b/brasilio_auth/management/commands/send_bulk_emails.py @@ -43,9 +43,6 @@ def handle(self, *args, **kwargs): "to": [row.to_email], } if not kwargs["dry_run"]: - django_rq.enqueue( - send_email, - **email_kwargs - ) + django_rq.enqueue(send_email, **email_kwargs) else: self.print_email_metadata(email_kwargs) diff --git a/brasilio_auth/tests/test_commands.py b/brasilio_auth/tests/test_commands.py index fd568ead..bbb41068 100644 --- a/brasilio_auth/tests/test_commands.py +++ b/brasilio_auth/tests/test_commands.py @@ -14,8 +14,7 @@ def setUp(self): with open(self.input_file.name, "w") as fobj: fobj.write( - "nome,data,to_email,subject\nnome_1,data_1,email_1,subject_1\n" - "nome_2,data_2,email_2,subject_2" + "nome,data,to_email,subject\nnome_1,data_1,email_1,subject_1\n" "nome_2,data_2,email_2,subject_2" ) with open(self.email_template.name, "w") as fobj: @@ -36,7 +35,7 @@ def setUp(self): "subject": "subject_2", "to": ["email_2"], "from_email": settings.DEFAULT_FROM_EMAIL, - } + }, ] def assert_sent_email_metadata(self, email, metadata): @@ -52,12 +51,7 @@ def test_send_bulk_emails(self): def test_send_email_custom_from_email(self): kwargs = {"from": "Example Email "} - call_command( - "send_bulk_emails", - self.input_file.name, - self.email_template.name, - **kwargs - ) + call_command("send_bulk_emails", self.input_file.name, self.email_template.name, **kwargs) self.expexted_send_email[0]["from_email"] = kwargs["from"] self.expexted_send_email[1]["from_email"] = kwargs["from"] @@ -75,11 +69,6 @@ def test_assert_mandatory_fields(self): call_command("send_bulk_emails", self.input_file.name, self.email_template.name) def test_do_not_send_mail(self): - call_command( - "send_bulk_emails", - self.input_file.name, - self.email_template.name, - "--dry-run" - ) + call_command("send_bulk_emails", self.input_file.name, self.email_template.name, "--dry-run") assert len(mail.outbox) == 0 diff --git a/core/email.py b/core/email.py index 8dd793a7..4b399ee3 100644 --- a/core/email.py +++ b/core/email.py @@ -2,11 +2,5 @@ def send_email(subject, body, from_email, to, **kwargs): - email = EmailMessage( - subject=subject, - body=body, - from_email=from_email, - to=to, - **kwargs - ) + email = EmailMessage(subject=subject, body=body, from_email=from_email, to=to, **kwargs) email.send() diff --git a/core/tests/test_email.py b/core/tests/test_email.py index ac3f48f4..7e2a22f4 100644 --- a/core/tests/test_email.py +++ b/core/tests/test_email.py @@ -13,11 +13,7 @@ def test_send_emnail(self): reply_to = ["Reply To Date: Fri, 16 Apr 2021 01:56:20 -0300 Subject: [PATCH 08/13] =?UTF-8?q?Adiciona=20par=C3=A2metro=20wait=5Ftime?= =?UTF-8?q?=20no=20send=5Fbulk=5Femails?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../management/commands/send_bulk_emails.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/brasilio_auth/management/commands/send_bulk_emails.py b/brasilio_auth/management/commands/send_bulk_emails.py index 38cacc61..a531f960 100644 --- a/brasilio_auth/management/commands/send_bulk_emails.py +++ b/brasilio_auth/management/commands/send_bulk_emails.py @@ -3,6 +3,7 @@ from django.conf import settings from django.core.management.base import BaseCommand from django.template import Context, Template +from tqdm import tqdm from core.email import send_email @@ -19,10 +20,11 @@ def print_email_metadata(self, metadata): print("-" * 80) def add_arguments(self, parser): + parser.add_argument("--sender", default=settings.DEFAULT_FROM_EMAIL) + parser.add_argument("--dry-run", default=False, action="store_true") + parser.add_argument("--wait-time", default=15) parser.add_argument("input_filename") - parser.add_argument("email_template") - parser.add_argument("--from") - parser.add_argument("-d", "--dry-run", default=False, action="store_true") + parser.add_argument("template_filename") def handle(self, *args, **kwargs): input_filename = kwargs["input_filename"] @@ -30,10 +32,11 @@ def handle(self, *args, **kwargs): error_msg = "Arquivo CSV deve conter campos 'to_email' e 'subject'" assert {"to_email", "subject"}.issubset(set(table.field_names)), error_msg - template_obj = self.load_email_template(kwargs["email_template"]) - from_email = kwargs["from"] or settings.DEFAULT_FROM_EMAIL + template_obj = self.load_email_template(kwargs["template_filename"]) + wait_time = kwargs["wait_time"] + from_email = kwargs["sender"] - for row in table: + for row in tqdm(table): context = Context(row._asdict()) rendered_template = template_obj.render(context=context) email_kwargs = { From 8c5313da65c36b8a11867f6f06d91381754893a1 Mon Sep 17 00:00:00 2001 From: Rhenan Bartels Date: Thu, 22 Apr 2021 09:53:29 -0300 Subject: [PATCH 09/13] Envia email a partir de um scheduler --- .../management/commands/send_bulk_emails.py | 13 +++-- brasilio_auth/tests/test_commands.py | 52 ++++++++++++++----- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/brasilio_auth/management/commands/send_bulk_emails.py b/brasilio_auth/management/commands/send_bulk_emails.py index a531f960..a6ea0540 100644 --- a/brasilio_auth/management/commands/send_bulk_emails.py +++ b/brasilio_auth/management/commands/send_bulk_emails.py @@ -1,8 +1,11 @@ -import django_rq +from datetime import timedelta + import rows from django.conf import settings from django.core.management.base import BaseCommand from django.template import Context, Template +from rq import Queue +from redis import Redis from tqdm import tqdm from core.email import send_email @@ -22,11 +25,13 @@ def print_email_metadata(self, metadata): def add_arguments(self, parser): parser.add_argument("--sender", default=settings.DEFAULT_FROM_EMAIL) parser.add_argument("--dry-run", default=False, action="store_true") - parser.add_argument("--wait-time", default=15) + parser.add_argument("--wait-time", default=15, type=int) parser.add_argument("input_filename") parser.add_argument("template_filename") def handle(self, *args, **kwargs): + queue = Queue(name=settings.DEFAULT_QUEUE_NAME, connection=Redis()) + input_filename = kwargs["input_filename"] table = rows.import_from_csv(input_filename) error_msg = "Arquivo CSV deve conter campos 'to_email' e 'subject'" @@ -36,6 +41,7 @@ def handle(self, *args, **kwargs): wait_time = kwargs["wait_time"] from_email = kwargs["sender"] + time_offset = 0 for row in tqdm(table): context = Context(row._asdict()) rendered_template = template_obj.render(context=context) @@ -46,6 +52,7 @@ def handle(self, *args, **kwargs): "to": [row.to_email], } if not kwargs["dry_run"]: - django_rq.enqueue(send_email, **email_kwargs) + queue.enqueue_in(timedelta(seconds=time_offset), send_email, **email_kwargs) + time_offset += wait_time else: self.print_email_metadata(email_kwargs) diff --git a/brasilio_auth/tests/test_commands.py b/brasilio_auth/tests/test_commands.py index bbb41068..8090bdf3 100644 --- a/brasilio_auth/tests/test_commands.py +++ b/brasilio_auth/tests/test_commands.py @@ -1,14 +1,22 @@ +from datetime import timedelta from tempfile import NamedTemporaryFile +from unittest import mock import pytest from django.conf import settings -from django.core import mail from django.core.management import call_command from django.test import TestCase +from core.email import send_email + class TestSendBulkEmails(TestCase): def setUp(self): + self.p_queue_cls = mock.patch("brasilio_auth.management.commands.send_bulk_emails.Queue") + self.m_queue_cls = self.p_queue_cls.start() + self.m_queue = mock.Mock() + self.m_queue_cls.return_value = self.m_queue + self.input_file = NamedTemporaryFile(suffix=".csv") self.email_template = NamedTemporaryFile(suffix=".txt") @@ -38,27 +46,32 @@ def setUp(self): }, ] - def assert_sent_email_metadata(self, email, metadata): - for key, value in metadata.items(): - assert email.__getattribute__(key) == value + def tearDown(self): + self.p_queue_cls.stop() def test_send_bulk_emails(self): call_command("send_bulk_emails", self.input_file.name, self.email_template.name) - assert len(mail.outbox) == 2 - self.assert_sent_email_metadata(mail.outbox[0], self.expexted_send_email[0]) - self.assert_sent_email_metadata(mail.outbox[1], self.expexted_send_email[1]) + self.m_queue.enqueue_in.assert_has_calls( + [ + mock.call(timedelta(seconds=0), send_email, **self.expexted_send_email[0]), + mock.call(timedelta(seconds=15), send_email, **self.expexted_send_email[1]), + ] + ) def test_send_email_custom_from_email(self): - kwargs = {"from": "Example Email "} + kwargs = {"sender": "Example Email "} call_command("send_bulk_emails", self.input_file.name, self.email_template.name, **kwargs) - self.expexted_send_email[0]["from_email"] = kwargs["from"] - self.expexted_send_email[1]["from_email"] = kwargs["from"] + self.expexted_send_email[0]["from_email"] = kwargs["sender"] + self.expexted_send_email[1]["from_email"] = kwargs["sender"] - assert len(mail.outbox) == 2 - self.assert_sent_email_metadata(mail.outbox[0], self.expexted_send_email[0]) - self.assert_sent_email_metadata(mail.outbox[1], self.expexted_send_email[1]) + self.m_queue.enqueue_in.assert_has_calls( + [ + mock.call(timedelta(seconds=0), send_email, **self.expexted_send_email[0]), + mock.call(timedelta(seconds=15), send_email, **self.expexted_send_email[1]), + ] + ) def test_assert_mandatory_fields(self): with open(self.input_file.name, "w") as fobj: @@ -71,4 +84,15 @@ def test_assert_mandatory_fields(self): def test_do_not_send_mail(self): call_command("send_bulk_emails", self.input_file.name, self.email_template.name, "--dry-run") - assert len(mail.outbox) == 0 + self.m_queue.assert_not_called() + + def test_send_bulk_emails_schedule_with_wait_time(self): + kwargs = {"wait_time": 30} + call_command("send_bulk_emails", self.input_file.name, self.email_template.name, **kwargs) + + self.m_queue.enqueue_in.assert_has_calls( + [ + mock.call(timedelta(seconds=0), send_email, **self.expexted_send_email[0]), + mock.call(timedelta(seconds=30), send_email, **self.expexted_send_email[1]), + ] + ) From 971d543688c41309843b74851750055b25da1c75 Mon Sep 17 00:00:00 2001 From: Rhenan Bartels Date: Thu, 22 Apr 2021 09:57:28 -0300 Subject: [PATCH 10/13] Adiciona DEFAULT_QUEUE_NAME aos settings --- brasilio/settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/brasilio/settings.py b/brasilio/settings.py index c5983b6f..af510cc8 100644 --- a/brasilio/settings.py +++ b/brasilio/settings.py @@ -242,7 +242,8 @@ } # django-rq config -RQ_QUEUES = {"default": {"URL": REDIS_URL, "DEFAULT_TIMEOUT": 500,}} +DEFAULT_QUEUE_NAME = "default" +RQ_QUEUES = {DEFAULT_QUEUE_NAME: {"URL": REDIS_URL, "DEFAULT_TIMEOUT": 500,}} RQ = { "DEFAULT_RESULT_TTL": 60 * 60 * 24, # 24-hours } From ac3ba7e6d26321ccc7d2c80948e47785b5b11bf7 Mon Sep 17 00:00:00 2001 From: Rhenan Bartels Date: Fri, 23 Apr 2021 09:45:58 -0300 Subject: [PATCH 11/13] Informa explicitamente URL do Redis --- brasilio_auth/management/commands/send_bulk_emails.py | 3 ++- brasilio_auth/tests/test_commands.py | 6 ++++++ requirements.txt | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/brasilio_auth/management/commands/send_bulk_emails.py b/brasilio_auth/management/commands/send_bulk_emails.py index a6ea0540..4edfe852 100644 --- a/brasilio_auth/management/commands/send_bulk_emails.py +++ b/brasilio_auth/management/commands/send_bulk_emails.py @@ -30,7 +30,8 @@ def add_arguments(self, parser): parser.add_argument("template_filename") def handle(self, *args, **kwargs): - queue = Queue(name=settings.DEFAULT_QUEUE_NAME, connection=Redis()) + redis_conn = Redis.from_url(settings.RQ_QUEUES[settings.DEFAULT_QUEUE_NAME]["URL"]) + queue = Queue(name=settings.DEFAULT_QUEUE_NAME, connection=redis_conn) input_filename = kwargs["input_filename"] table = rows.import_from_csv(input_filename) diff --git a/brasilio_auth/tests/test_commands.py b/brasilio_auth/tests/test_commands.py index 8090bdf3..347ac787 100644 --- a/brasilio_auth/tests/test_commands.py +++ b/brasilio_auth/tests/test_commands.py @@ -6,12 +6,16 @@ from django.conf import settings from django.core.management import call_command from django.test import TestCase +from redis import Redis from core.email import send_email class TestSendBulkEmails(TestCase): def setUp(self): + self.p_redis_from_url = mock.patch.object(Redis, "from_url") + self.m_redis_from_url = self.p_redis_from_url.start() + self.p_queue_cls = mock.patch("brasilio_auth.management.commands.send_bulk_emails.Queue") self.m_queue_cls = self.p_queue_cls.start() self.m_queue = mock.Mock() @@ -48,10 +52,12 @@ def setUp(self): def tearDown(self): self.p_queue_cls.stop() + self.m_redis_from_url.stop() def test_send_bulk_emails(self): call_command("send_bulk_emails", self.input_file.name, self.email_template.name) + self.m_redis_from_url.assert_called_once_with(settings.RQ_QUEUES[settings.DEFAULT_QUEUE_NAME]["URL"]) self.m_queue.enqueue_in.assert_has_calls( [ mock.call(timedelta(seconds=0), send_email, **self.expexted_send_email[0]), diff --git a/requirements.txt b/requirements.txt index 8de7afed..27707e32 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,6 +31,7 @@ openpyxl==3.0.5 psycopg2-binary==2.8.5 pytest-django==3.2.1 pytz==2020.1 +redis==3.5.3 requests-cache==0.5.2 requests==2.24.0 retry==0.9.2 From 37ba869056ed14a962f19b0121b561deae88c093 Mon Sep 17 00:00:00 2001 From: Rhenan Bartels Date: Fri, 23 Apr 2021 10:19:31 -0300 Subject: [PATCH 12/13] =?UTF-8?q?Cria=20fun=C3=A7=C3=A3o=20para=20retornar?= =?UTF-8?q?=20fila=20do=20Redis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../management/commands/send_bulk_emails.py | 6 ++-- brasilio_auth/tests/test_commands.py | 18 +++++------ core/queue.py | 7 +++++ core/tests/test_queue.py | 31 +++++++++++++++++++ 4 files changed, 48 insertions(+), 14 deletions(-) create mode 100644 core/queue.py create mode 100644 core/tests/test_queue.py diff --git a/brasilio_auth/management/commands/send_bulk_emails.py b/brasilio_auth/management/commands/send_bulk_emails.py index 4edfe852..f96ae323 100644 --- a/brasilio_auth/management/commands/send_bulk_emails.py +++ b/brasilio_auth/management/commands/send_bulk_emails.py @@ -4,11 +4,10 @@ from django.conf import settings from django.core.management.base import BaseCommand from django.template import Context, Template -from rq import Queue -from redis import Redis from tqdm import tqdm from core.email import send_email +from core.queue import get_redis_queue class Command(BaseCommand): @@ -30,8 +29,7 @@ def add_arguments(self, parser): parser.add_argument("template_filename") def handle(self, *args, **kwargs): - redis_conn = Redis.from_url(settings.RQ_QUEUES[settings.DEFAULT_QUEUE_NAME]["URL"]) - queue = Queue(name=settings.DEFAULT_QUEUE_NAME, connection=redis_conn) + queue = get_redis_queue(settings.DEFAULT_QUEUE_NAME, settings.RQ_QUEUES[settings.DEFAULT_QUEUE_NAME]["URL"]) input_filename = kwargs["input_filename"] table = rows.import_from_csv(input_filename) diff --git a/brasilio_auth/tests/test_commands.py b/brasilio_auth/tests/test_commands.py index 347ac787..64210f65 100644 --- a/brasilio_auth/tests/test_commands.py +++ b/brasilio_auth/tests/test_commands.py @@ -6,20 +6,16 @@ from django.conf import settings from django.core.management import call_command from django.test import TestCase -from redis import Redis from core.email import send_email class TestSendBulkEmails(TestCase): def setUp(self): - self.p_redis_from_url = mock.patch.object(Redis, "from_url") - self.m_redis_from_url = self.p_redis_from_url.start() - - self.p_queue_cls = mock.patch("brasilio_auth.management.commands.send_bulk_emails.Queue") - self.m_queue_cls = self.p_queue_cls.start() + self.p_redis_queue = mock.patch("brasilio_auth.management.commands.send_bulk_emails.get_redis_queue") + self.m_redis_queue = self.p_redis_queue.start() self.m_queue = mock.Mock() - self.m_queue_cls.return_value = self.m_queue + self.m_redis_queue.return_value = self.m_queue self.input_file = NamedTemporaryFile(suffix=".csv") self.email_template = NamedTemporaryFile(suffix=".txt") @@ -51,13 +47,15 @@ def setUp(self): ] def tearDown(self): - self.p_queue_cls.stop() - self.m_redis_from_url.stop() + self.p_redis_queue.stop() def test_send_bulk_emails(self): call_command("send_bulk_emails", self.input_file.name, self.email_template.name) - self.m_redis_from_url.assert_called_once_with(settings.RQ_QUEUES[settings.DEFAULT_QUEUE_NAME]["URL"]) + self.m_redis_queue.assert_called_once_with( + settings.DEFAULT_QUEUE_NAME, + settings.RQ_QUEUES[settings.DEFAULT_QUEUE_NAME]["URL"] + ) self.m_queue.enqueue_in.assert_has_calls( [ mock.call(timedelta(seconds=0), send_email, **self.expexted_send_email[0]), diff --git a/core/queue.py b/core/queue.py new file mode 100644 index 00000000..e80be278 --- /dev/null +++ b/core/queue.py @@ -0,0 +1,7 @@ +from rq import Queue +from redis import Redis + + +def get_redis_queue(name: str, url: str) -> Queue: + redis_conn = Redis.from_url(url) + return Queue(name=name, connection=redis_conn) diff --git a/core/tests/test_queue.py b/core/tests/test_queue.py new file mode 100644 index 00000000..7f0b0e77 --- /dev/null +++ b/core/tests/test_queue.py @@ -0,0 +1,31 @@ +from unittest import mock + +from django.test import TestCase +from redis import Redis +from rq import Queue + +from core.queue import get_redis_queue + + +class TestGetRedisQueue(TestCase): + def setUp(self): + self.m_redis_conn = mock.Mock() + self.p_redis_from_url = mock.patch.object(Redis, "from_url") + self.m_redis_from_url = self.p_redis_from_url.start() + self.m_redis_from_url.return_value = self.m_redis_conn + + self.m_queue = mock.Mock(spec=Queue) + self.p_queue_cls = mock.patch("core.queue.Queue") + self.m_queue_cls = self.p_queue_cls.start() + self.m_queue_cls.return_value = self.m_queue + + def tearDown(self): + self.p_queue_cls.stop() + self.m_redis_from_url.stop() + + def test_get_redis_queue_with_custom_arguments(self): + queue = get_redis_queue(name="name", url="redis://url:6379") + + assert isinstance(queue, Queue) + self.m_redis_from_url.assert_called_once_with("redis://url:6379") + self.m_queue_cls.assert_called_once_with(name="name", connection=self.m_redis_conn) From 1e5782794673d513a946cd1235a5b5bcda9d67ec Mon Sep 17 00:00:00 2001 From: Rhenan Bartels Date: Fri, 23 Apr 2021 10:23:04 -0300 Subject: [PATCH 13/13] =?UTF-8?q?Envia=20email=20de=20contato=20de=20forma?= =?UTF-8?q?=20ass=C3=ADncrona?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/views.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/views.py b/core/views.py index 126b242a..bbdcef6f 100644 --- a/core/views.py +++ b/core/views.py @@ -14,12 +14,16 @@ from core.forms import ContactForm, DatasetSearchForm, get_table_dynamic_form from core.middlewares import disable_non_logged_user_cache from core.models import Dataset, Table +from core.queue import get_redis_queue from core.templatetags.utils import obfuscate from core.util import cached_http_get_json from data_activities_log.activites import recent_activities from traffic_control.logging import log_blocked_request +QUEUE = get_redis_queue(settings.DEFAULT_QUEUE_NAME, settings.RQ_QUEUES[settings.DEFAULT_QUEUE_NAME]["URL"]) + + class Echo: def write(self, value): return value @@ -41,13 +45,15 @@ def contact(request): if form.is_valid(): data = form.cleaned_data - send_email( + QUEUE.enqueue( + send_email, subject=f"Contato no Brasil.IO: {data['name']}", body=data["message"], from_email=f'{data["name"]} (via Brasil.IO) <{settings.DEFAULT_FROM_EMAIL}>', to=[settings.DEFAULT_FROM_EMAIL], reply_to=[f'{data["name"]} <{data["email"]}>'], ) + return redirect(reverse("core:contact") + "?sent=true") else: