From 4f81c52cf33c9f9df4e94cbf08a3e624573a53c9 Mon Sep 17 00:00:00 2001 From: Sergey Lavrinenko Date: Sun, 1 Mar 2015 12:49:55 +0300 Subject: [PATCH 1/4] README update --- README.rst | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index b7c4fff..59b8239 100644 --- a/README.rst +++ b/README.rst @@ -93,17 +93,27 @@ Send and get response from smtp server: .. code-block:: python r = message.send(to=('John Brown', 'jbrown@gmail.com'), + render={'field1': 'X'}, smtp={'host':'smtp.mycompany.com', 'port': 465, 'ssl': True}) assert r.status_code == 250 -Or send via Django email backend: + +Django +------ + +DjangoMessage helper sends via django configured email backend: .. code-block:: python - from django.core.mail import get_connection - from emails.message import DjangoMessageProxy - c = django.core.mail.get_connection() - c.send_messages([DjangoMessageProxy(message), ]) + from emails.django_ import DjangoMessage as Message + message = Message(...) + message.send(mail_to=('John Brown', 'jbrown@gmail.com'), + context={'field1': 'X'}) + +Flask +----- + +For flask integration take a look at `flask-emails `_ HTML transformer From 4147282b13db27ef84aa8d7567d525d32d1d466b Mon Sep 17 00:00:00 2001 From: Sergey Lavrinenko Date: Tue, 3 Mar 2015 14:06:15 +0300 Subject: [PATCH 2/4] tests update: real images added --- emails/testsuite/message/data/pushkin.jpg | Bin 0 -> 12910 bytes emails/testsuite/message/helpers.py | 8 +++++--- 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 emails/testsuite/message/data/pushkin.jpg diff --git a/emails/testsuite/message/data/pushkin.jpg b/emails/testsuite/message/data/pushkin.jpg new file mode 100644 index 0000000000000000000000000000000000000000..744c0eb22f8b58df79ea9aa531aefb881b436e62 GIT binary patch literal 12910 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 Date: Tue, 3 Mar 2015 20:26:27 +0300 Subject: [PATCH 3/4] Check if html is valid in zip- and directory- loaders --- emails/loader/__init__.py | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/emails/loader/__init__.py b/emails/loader/__init__.py index d50697a..4989e4e 100644 --- a/emails/loader/__init__.py +++ b/emails/loader/__init__.py @@ -1,6 +1,7 @@ # encoding: utf-8 import os import os.path +from emails.loader.local_store import FileNotFound from emails.compat import to_unicode from emails.compat import urlparse from emails import Message @@ -9,6 +10,18 @@ from emails.loader.helpers import guess_charset +class LoadError(Exception): + pass + + +class IndexFileNotFound(LoadError): + pass + + +class InvalidHtmlFile(LoadError): + pass + + def from_url(url, message_params=None, requests_params=None, **kwargs): def _make_base_url(url): @@ -37,7 +50,13 @@ def _make_base_url(url): def from_directory(directory, index_file=None, message_params=None, **kwargs): store = local_store.FileSystemLoader(searchpath=directory) - index_file_name = store.find_index_file(index_file) + + try: + index_file_name = store.find_index_file(index_file) + except FileNotFound: + # reraise another exception + raise IndexFileNotFound('html file not found') + dirname, _ = os.path.split(index_file_name) if dirname: store.base_path = dirname @@ -45,6 +64,8 @@ def from_directory(directory, index_file=None, message_params=None, **kwargs): message_params = message_params or {} message = Message(html=store.content(index_file_name, is_html=True, guess_charset=True), **message_params) message.create_transformer(local_loader=store, requests_params=kwargs.pop('requests_params', None)) + if message.transformer.tree is None: + raise InvalidHtmlFile("Error parsing file '%s'" % index_file_name) message.transformer.load_and_transform(**kwargs) message.transformer.save() return message @@ -56,14 +77,22 @@ def from_file(filename, **kwargs): def from_zip(zip_file, message_params=None, **kwargs): store = local_store.ZipLoader(file=zip_file) - index_file_name = store.find_index_file() - dirname, index_file_name = os.path.split(index_file_name) + + try: + origin_index_file_name = store.find_index_file() + except FileNotFound: + # reraise another exception + raise IndexFileNotFound('html file not found') + + dirname, index_file_name = os.path.split(origin_index_file_name) if dirname: store.base_path = dirname message_params = message_params or {} message = Message(html=store.content(index_file_name, is_html=True, guess_charset=True), **message_params) message.create_transformer(local_loader=store, requests_params=kwargs.pop('requests_params', None)) + if message.transformer.tree is None: + raise InvalidHtmlFile("Error parsing file '%s'" % origin_index_file_name) message.transformer.load_and_transform(**kwargs) message.transformer.save() return message From 7cc80fcaa9c1be4c888d3c76f2d15470340f8c81 Mon Sep 17 00:00:00 2001 From: Sergey Lavrinenko Date: Tue, 3 Mar 2015 20:38:30 +0300 Subject: [PATCH 4/4] Ignore hidden files when import from files. Fixes #24 --- emails/loader/local_store.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/emails/loader/local_store.py b/emails/loader/local_store.py index 16eb237..c0f58cc 100644 --- a/emails/loader/local_store.py +++ b/emails/loader/local_store.py @@ -83,8 +83,9 @@ def find_index_file(self, filename=None): else: html_files.append(filename) - if html_files: - return html_files[0] + # Ignore hidden files (filename started with dot) + for fn in filter(lambda p: not os.path.basename(p).startswith('.'), html_files): + return fn raise FileNotFound('index html')