From f52326f93eaa5fd91dd40e52380ab24b5736b71f Mon Sep 17 00:00:00 2001 From: Jaspreet-singh-1032 Date: Sun, 26 Mar 2023 19:54:05 +0530 Subject: [PATCH 1/2] use template to send mail for export job completion --- docker-compose.yaml | 2 +- example/poetry.lock | 16 ++++- example/project/__init__.py | 3 + example/project/settings.py | 2 +- example/pyproject.toml | 1 + example/winners/tests/test_utils.py | 43 ++++++++++++ import_export_celery/tasks.py | 22 +----- .../email/export_job_completion.html | 14 ++++ import_export_celery/utils.py | 70 +++++++++++++++++++ 9 files changed, 149 insertions(+), 24 deletions(-) create mode 100644 example/winners/tests/test_utils.py create mode 100644 import_export_celery/templates/email/export_job_completion.html create mode 100644 import_export_celery/utils.py diff --git a/docker-compose.yaml b/docker-compose.yaml index 4b55d6e..51fe731 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -17,7 +17,7 @@ services: - ./pyenv:/home/test celery: build: . - entrypoint: poetry run celery -A project.celery worker + entrypoint: poetry run celery -A project.celery worker -l info links: - postgres - redis diff --git a/example/poetry.lock b/example/poetry.lock index 8ca7557..68bc5d2 100644 --- a/example/poetry.lock +++ b/example/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. [[package]] name = "amqp" @@ -321,6 +321,18 @@ files = [ {file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"}, ] +[[package]] +name = "html2text" +version = "2020.1.16" +description = "Turn HTML into equivalent Markdown-structured text." +category = "main" +optional = false +python-versions = ">=3.5" +files = [ + {file = "html2text-2020.1.16-py3-none-any.whl", hash = "sha256:c7c629882da0cf377d66f073329ccf34a12ed2adf0169b9285ae4e63ef54c82b"}, + {file = "html2text-2020.1.16.tar.gz", hash = "sha256:e296318e16b059ddb97f7a8a1d6a5c1d7af4544049a01e261731d2d5cc277bbb"}, +] + [[package]] name = "importlib-metadata" version = "4.8.1" @@ -824,4 +836,4 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black ( [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "3803687c30191b8f372989a64507765e2f5ebe438ff55483db56e242d3a33488" +content-hash = "a3648819f33a1e0bf8777c6ea2b3404fd85ab3102dce0a6ccde9547efe96f9ff" diff --git a/example/project/__init__.py b/example/project/__init__.py index e69de29..53f4ccb 100644 --- a/example/project/__init__.py +++ b/example/project/__init__.py @@ -0,0 +1,3 @@ +from .celery import app as celery_app + +__all__ = ("celery_app",) diff --git a/example/project/settings.py b/example/project/settings.py index ca89a8a..58b6c6a 100644 --- a/example/project/settings.py +++ b/example/project/settings.py @@ -58,7 +58,7 @@ TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [], + "DIRS": ["templates"], "APP_DIRS": True, "OPTIONS": { "context_processors": [ diff --git a/example/pyproject.toml b/example/pyproject.toml index dfd7f27..745b5e7 100644 --- a/example/pyproject.toml +++ b/example/pyproject.toml @@ -13,6 +13,7 @@ celery = "*" django-author = "*" redis = "*" psycopg2-binary = "^2.8.4" +html2text = "^2020.1.16" [tool.poetry.dev-dependencies] django-admin-smoke-tests = "^0.3.0" diff --git a/example/winners/tests/test_utils.py b/example/winners/tests/test_utils.py new file mode 100644 index 0000000..e8565a1 --- /dev/null +++ b/example/winners/tests/test_utils.py @@ -0,0 +1,43 @@ +from django.test import TestCase, override_settings + +from django.urls import reverse +from import_export_celery.utils import ( + get_export_job_mail_subject, + get_export_job_mail_template, + get_export_job_mail_context, + DEFAULT_EXPORT_JOB_COMPLETION_MAIL_SUBJECT, + DEFAULT_EXPORT_JOB_COMPLETION_MAIL_TEMPLATE, +) +from import_export_celery.models import ExportJob + + +class UtilsTestCases(TestCase): + def test_get_export_job_mail_subject_by_default(self): + self.assertEqual( + DEFAULT_EXPORT_JOB_COMPLETION_MAIL_SUBJECT, get_export_job_mail_subject() + ) + + @override_settings(EXPORT_JOB_COMPLETION_MAIL_SUBJECT="New subject") + def test_get_export_job_mail_subject_overridden(self): + self.assertEqual("New subject", get_export_job_mail_subject()) + + def test_get_export_job_mail_template_default(self): + self.assertEqual( + DEFAULT_EXPORT_JOB_COMPLETION_MAIL_TEMPLATE, get_export_job_mail_template() + ) + + @override_settings(EXPORT_JOB_COMPLETION_MAIL_TEMPLATE="mytemplate.html") + def test_get_export_job_mail_template_overridden(self): + self.assertEqual("mytemplate.html", get_export_job_mail_template()) + + def test_get_export_job_mail_context(self): + export_job = ExportJob.objects.create( + app_label="winners", model="Winner", site_of_origin="http://127.0.0.1:8000" + ) + context = get_export_job_mail_context(export_job) + expected_context = { + "app_label": "winners", + "model": "Winner", + "link": f"http://127.0.0.1:8000/adminimport_export_celery/exportjob/{export_job.id}/change/", + } + self.assertEqual(context, expected_context) diff --git a/import_export_celery/tasks.py b/import_export_celery/tasks.py index c7e9b93..73c99eb 100644 --- a/import_export_celery/tasks.py +++ b/import_export_celery/tasks.py @@ -17,6 +17,7 @@ from . import models from .model_config import ModelConfig +from .utils import send_export_job_completion_mail from celery.utils.log import get_task_logger import logging @@ -234,24 +235,5 @@ def export_resource(self, *args, **kwargs): serialized = serialized.encode("utf8") export_job.file.save(filename, ContentFile(serialized)) if export_job.email_on_completion: - send_mail( - _("Django: Export job completed"), - _( - "Your export job on model {app_label}.{model} has completed. You can download the file at the following link:\n\n{link}" - ).format( - app_label=export_job.app_label, - model=export_job.model, - link=export_job.site_of_origin - + reverse( - "admin:%s_%s_change" - % ( - export_job._meta.app_label, - export_job._meta.model_name, - ), - args=[export_job.pk], - ), - ), - settings.SERVER_EMAIL, - [export_job.updated_by.email], - ) + send_export_job_completion_mail(export_job) return diff --git a/import_export_celery/templates/email/export_job_completion.html b/import_export_celery/templates/email/export_job_completion.html new file mode 100644 index 0000000..845c8c5 --- /dev/null +++ b/import_export_celery/templates/email/export_job_completion.html @@ -0,0 +1,14 @@ + + + + + + + Document + + + +

Your export job on model {{app_label}}.{{model}} has completed. You can download the file at the following link:

+ {{link}} + + diff --git a/import_export_celery/utils.py b/import_export_celery/utils.py new file mode 100644 index 0000000..f1d4f1d --- /dev/null +++ b/import_export_celery/utils.py @@ -0,0 +1,70 @@ +import html2text +from django.core.mail import send_mail +from django.template.loader import get_template +from django.conf import settings +from django.urls import reverse + +DEFAULT_EXPORT_JOB_COMPLETION_MAIL_SUBJECT = "Django: Export job completed" +DEFAULT_EXPORT_JOB_COMPLETION_MAIL_TEMPLATE = "email/export_job_completion.html" + + +def build_html_and_text_message(template_name, context={}): + """ + Render the given template with the context and returns + the data in html and plain text format. + """ + template = get_template(template_name) + html_message = template.render(context) + text_message = html2text.html2text(html_message) + return html_message, text_message + + +def get_export_job_mail_context(export_job): + context = { + "app_label": export_job.app_label, + "model": export_job.model, + "link": export_job.site_of_origin + + reverse( + "admin:%s_%s_change" + % ( + export_job._meta.app_label, + export_job._meta.model_name, + ), + args=[export_job.pk], + ), + } + return context + + +def get_export_job_mail_subject(): + return getattr( + settings, + "EXPORT_JOB_COMPLETION_MAIL_SUBJECT", + DEFAULT_EXPORT_JOB_COMPLETION_MAIL_SUBJECT, + ) + + +def get_export_job_mail_template(): + return getattr( + settings, + "EXPORT_JOB_COMPLETION_MAIL_TEMPLATE", + DEFAULT_EXPORT_JOB_COMPLETION_MAIL_TEMPLATE, + ) + + +def send_export_job_completion_mail(export_job): + """ + Send export job completion mail + """ + subject = get_export_job_mail_subject() + template_name = get_export_job_mail_template() + context = get_export_job_mail_context(export_job) + context.update({"export_job": export_job}) + html_message, text_message = build_html_and_text_message(template_name, context) + send_mail( + subject=subject, + message=text_message, + html_message=html_message, + from_email=settings.SERVER_EMAIL, + recipient_list=[export_job.updated_by.email], + ) From e347b0f2b9266de0259d9be85294c6ff5a99e158 Mon Sep 17 00:00:00 2001 From: Jaspreet-singh-1032 Date: Sun, 26 Mar 2023 21:59:33 +0530 Subject: [PATCH 2/2] fixed flake8 issues and added html2text to setup.py dependencies --- example/winners/tests/test_utils.py | 1 - import_export_celery/tasks.py | 2 -- setup.cfg | 1 + setup.py | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/example/winners/tests/test_utils.py b/example/winners/tests/test_utils.py index e8565a1..1017f7e 100644 --- a/example/winners/tests/test_utils.py +++ b/example/winners/tests/test_utils.py @@ -1,6 +1,5 @@ from django.test import TestCase, override_settings -from django.urls import reverse from import_export_celery.utils import ( get_export_job_mail_subject, get_export_job_mail_template, diff --git a/import_export_celery/tasks.py b/import_export_celery/tasks.py index 73c99eb..6321fe4 100644 --- a/import_export_celery/tasks.py +++ b/import_export_celery/tasks.py @@ -7,9 +7,7 @@ from django.conf import settings from django.core.files.base import ContentFile from django.core.cache import cache -from django.core.mail import send_mail -from django.urls import reverse from django.utils.encoding import force_str from django.utils.translation import gettext_lazy as _ diff --git a/setup.cfg b/setup.cfg index 406b63e..8d4f8ec 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,7 @@ [flake8] max-line-length = 150 exclude = + pyenv/*, env/*, env-dj19/*, *migrations/*, diff --git a/setup.py b/setup.py index df41556..142d237 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ here = os.path.abspath(os.path.dirname(__file__)) -requires = ["Django", "django-import-export", "django-author"] +requires = ["Django", "django-import-export", "django-author", "html2text"] try: version = subprocess.check_output(