From 4c8c18cfd26a0ef009599f43afb560cbe229d80a Mon Sep 17 00:00:00 2001 From: Artur Barseghyan Date: Thu, 28 Feb 2019 10:55:13 +0100 Subject: [PATCH] prepare 0.13.9 --- .gitignore | 1 + .hgignore | 1 + CHANGELOG.rst | 17 ++ Docker | 23 ++ MANIFEST.in | 1 + README.rst | 3 + examples/requirements/test.txt | 12 +- examples/simple/settings/base.py | 1 + scripts/install_chromedriver.sh | 7 + setup.py | 5 +- src/fobi/__init__.py | 2 +- .../form_handlers/mail_sender/README.rst | 34 +++ .../form_handlers/mail_sender/__init__.py | 12 + .../plugins/form_handlers/mail_sender/apps.py | 15 ++ .../plugins/form_handlers/mail_sender/base.py | 229 ++++++++++++++++++ .../plugins/form_handlers/mail_sender/conf.py | 30 +++ .../form_handlers/mail_sender/defaults.py | 18 ++ .../mail_sender/fobi_form_handlers.py | 18 ++ .../form_handlers/mail_sender/forms.py | 71 ++++++ .../form_handlers/mail_sender/helpers.py | 36 +++ .../form_handlers/mail_sender/mixins.py | 131 ++++++++++ .../form_handlers/mail_sender/settings.py | 13 + .../mail_sender/plugin_data_repr.html | 3 + src/fobi/helpers.py | 1 + src/fobi/test.py | 17 -- src/fobi/tests/data.py | 17 +- .../tests/test_browser_build_dynamic_forms.py | 1 + 27 files changed, 687 insertions(+), 32 deletions(-) create mode 100644 Docker create mode 100644 scripts/install_chromedriver.sh create mode 100644 src/fobi/contrib/plugins/form_handlers/mail_sender/README.rst create mode 100644 src/fobi/contrib/plugins/form_handlers/mail_sender/__init__.py create mode 100644 src/fobi/contrib/plugins/form_handlers/mail_sender/apps.py create mode 100644 src/fobi/contrib/plugins/form_handlers/mail_sender/base.py create mode 100644 src/fobi/contrib/plugins/form_handlers/mail_sender/conf.py create mode 100644 src/fobi/contrib/plugins/form_handlers/mail_sender/defaults.py create mode 100644 src/fobi/contrib/plugins/form_handlers/mail_sender/fobi_form_handlers.py create mode 100644 src/fobi/contrib/plugins/form_handlers/mail_sender/forms.py create mode 100644 src/fobi/contrib/plugins/form_handlers/mail_sender/helpers.py create mode 100644 src/fobi/contrib/plugins/form_handlers/mail_sender/mixins.py create mode 100644 src/fobi/contrib/plugins/form_handlers/mail_sender/settings.py create mode 100644 src/fobi/contrib/plugins/form_handlers/mail_sender/templates/mail_sender/plugin_data_repr.html delete mode 100644 src/fobi/test.py diff --git a/.gitignore b/.gitignore index a115e741..5d38dd0e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ ghostdriver.log tags codebin.py examples/mezzanine_example/dev.db +geckodriver.log MANIFEST.in~ MIND_BUCKET.rst diff --git a/.hgignore b/.hgignore index b0e83acb..ab08b4be 100644 --- a/.hgignore +++ b/.hgignore @@ -13,6 +13,7 @@ syntax: regexp \.py,cover \.idea/ \.pytest_cache/ +\geckodriver\.log ^MANIFEST\.in~ ^tmp/ diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 24cbd791..84146745 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,10 +15,27 @@ are used for versioning (schema follows below): 0.3.4 to 0.4). - All backwards incompatible changes are mentioned in this document. +0.13.9 +------ +2019-02-28 + +.. note:: + + Release supported by `Goldmund, Wyldebeast & Wunderliebe + `_. + +- Add `mail_sender` form handler plugin. +- Upgrade test suite. + 0.13.8 ------ 2019-01-07 +.. note:: + + Release supported by `Goldmund, Wyldebeast & Wunderliebe + `_. + - Make it easier to redirect to a new URL (on success) in integration apps. 0.13.7 diff --git a/Docker b/Docker new file mode 100644 index 00000000..7418d130 --- /dev/null +++ b/Docker @@ -0,0 +1,23 @@ +# To build: +# > docker build -f Dockerfile . +FROM python:3.6-stretch +ENV DEBIAN_FRONTEND noninteractive + +# Install google chrome +RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add +RUN echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | tee /etc/apt/sources.list.d/google-chrome.list +RUN apt-get update && apt-get upgrade -y +RUN apt-get install -y libglib2.0-0=2.50.3-2 \ + libnss3=2:3.26.2-1.1+deb9u1 \ + libgconf-2-4=3.2.6-4+b1 \ + libfontconfig1=2.11.0-6.7+b1 +RUN apt-get install -y google-chrome-stable +RUN apt-get install -y mc + +# Install chrome driver +RUN wget -N http://chromedriver.storage.googleapis.com/2.42/chromedriver_linux64.zip +RUN unzip chromedriver_linux64.zip +RUN chmod +x chromedriver +RUN mv -f chromedriver /usr/local/share/chromedriver +RUN ln -s /usr/local/share/chromedriver /usr/local/bin/chromedriver +RUN ln -s /usr/local/share/chromedriver /usr/bin/chromedriver diff --git a/MANIFEST.in b/MANIFEST.in index d5d990a3..fa5f96a4 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -51,6 +51,7 @@ recursive-include src/fobi/contrib/plugins/form_elements/content/content_image_u recursive-include src/fobi/contrib/plugins/form_handlers/db_store/templates * recursive-include src/fobi/contrib/plugins/form_handlers/mail/templates * +recursive-include src/fobi/contrib/plugins/form_handlers/mail_sender/templates * recursive-include src/fobi/contrib/plugins/form_handlers/http_repost/templates * recursive-include src/fobi/contrib/plugins/form_importers/mailchimp_importer/templates * diff --git a/README.rst b/README.rst index ca117ad7..1347151b 100644 --- a/README.rst +++ b/README.rst @@ -1543,6 +1543,9 @@ README.rst file in directory of each plugin for details. - `Mail `__: Send the form data by email. +- `Mail the sender + `__: + Send the form data by email to the sender (submitter) of the form. Integration with third-party apps and frameworks ================================================ diff --git a/examples/requirements/test.txt b/examples/requirements/test.txt index 1c0d3e5b..9bdebfea 100644 --- a/examples/requirements/test.txt +++ b/examples/requirements/test.txt @@ -1,9 +1,9 @@ factory_boy==2.11.1 -Faker==0.8.17 +Faker==1.0.2 py==1.5.4 -pytest-cov==2.5.1 -pytest-django==3.3.3 -pytest-ordering==0.5 -pytest==3.7.1 +pytest-cov==2.6.1 +pytest-django==3.4.8 +pytest-ordering==0.6 +pytest==4.3.0 selenium==2.53.6 -tox==3.1.2 +tox==3.7.0 diff --git a/examples/simple/settings/base.py b/examples/simple/settings/base.py index 2a294d71..fbbfee76 100644 --- a/examples/simple/settings/base.py +++ b/examples/simple/settings/base.py @@ -350,6 +350,7 @@ INSTALLED_APPS = [ 'fobi.contrib.plugins.form_handlers.db_store', 'fobi.contrib.plugins.form_handlers.http_repost', 'fobi.contrib.plugins.form_handlers.mail', + 'fobi.contrib.plugins.form_handlers.mail_sender', # *********************************************************************** # *********************************************************************** diff --git a/scripts/install_chromedriver.sh b/scripts/install_chromedriver.sh new file mode 100644 index 00000000..c37233b1 --- /dev/null +++ b/scripts/install_chromedriver.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +wget -N http://chromedriver.storage.googleapis.com/2.42/chromedriver_linux64.zip +unzip chromedriver_linux64.zip +chmod +x chromedriver +sudo mv -f chromedriver /usr/local/share/chromedriver +sudo ln -s /usr/local/share/chromedriver /usr/local/bin/chromedriver +sudo ln -s /usr/local/share/chromedriver /usr/bin/chromedriver diff --git a/setup.py b/setup.py index 445cca86..e4c72aab 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ import sys from distutils.version import LooseVersion from setuptools import setup, find_packages -version = '0.13.8' +version = '0.13.9' # *************************************************************************** # ************************** Python version ********************************* @@ -140,6 +140,9 @@ template_dirs = [ # Mail "src/fobi/contrib/plugins/form_handlers/mail/templates/mail", + # Mail sender + "src/fobi/contrib/plugins/form_handlers/mail_sender/templates/mail_sender", + # Http re-post "src/fobi/contrib/plugins/form_handlers/http_repost/templates/" "http_repost", diff --git a/src/fobi/__init__.py b/src/fobi/__init__.py index f920733d..775bdaab 100644 --- a/src/fobi/__init__.py +++ b/src/fobi/__init__.py @@ -1,5 +1,5 @@ __title__ = 'django-fobi' -__version__ = '0.13.8' +__version__ = '0.13.9' __author__ = 'Artur Barseghyan ' __copyright__ = '2014-2018 Artur Barseghyan' __license__ = 'GPL 2.0/LGPL 2.1' diff --git a/src/fobi/contrib/plugins/form_handlers/mail_sender/README.rst b/src/fobi/contrib/plugins/form_handlers/mail_sender/README.rst new file mode 100644 index 00000000..a9139416 --- /dev/null +++ b/src/fobi/contrib/plugins/form_handlers/mail_sender/README.rst @@ -0,0 +1,34 @@ +fobi.contrib.plugins.form_handlers.mail_sender +---------------------------------------------- +A ``Fobi`` Mail form handler plugin. Submits the form +data by email to the sender (submitter) of the form. + +Installation +~~~~~~~~~~~~ +(1) Add ``fobi.contrib.plugins.form_handlers.mail_sender`` to the + ``INSTALLED_APPS`` in your ``settings.py``. + + .. code-block:: python + + INSTALLED_APPS = ( + # ... + 'fobi.contrib.plugins.form_handlers.mail_sender', + # ... + ) + +(2) In the terminal type: + + .. code-block:: sh + + ./manage.py fobi_sync_plugins + +(3) Assign appropriate permissions to the target users/groups to be using + the plugin if ``FOBI_RESTRICT_PLUGIN_ACCESS`` is set to True. + +Usage +~~~~~ +(1) Add the `Mail the sender` form handler to the form. +(2) In the value for `Form field name to email` fill in the form field name + which shall be used for sending an email to. For instance, if your + original form consisted of `name`, `email`, `comment` fields, you should + put `email` as a value for `Form field name to email` field. diff --git a/src/fobi/contrib/plugins/form_handlers/mail_sender/__init__.py b/src/fobi/contrib/plugins/form_handlers/mail_sender/__init__.py new file mode 100644 index 00000000..078e4482 --- /dev/null +++ b/src/fobi/contrib/plugins/form_handlers/mail_sender/__init__.py @@ -0,0 +1,12 @@ +from __future__ import absolute_import + +__title__ = 'fobi.contrib.plugins.form_handlers.mail_sender' +__author__ = 'Artur Barseghyan ' +__copyright__ = '2014-2018 Artur Barseghyan' +__license__ = 'GPL 2.0/LGPL 2.1' +__all__ = ('default_app_config', 'UID',) + +default_app_config = 'fobi.contrib.plugins.form_handlers.mail_sender.apps.' \ + 'Config' + +UID = 'mail_sender' diff --git a/src/fobi/contrib/plugins/form_handlers/mail_sender/apps.py b/src/fobi/contrib/plugins/form_handlers/mail_sender/apps.py new file mode 100644 index 00000000..d9c612c7 --- /dev/null +++ b/src/fobi/contrib/plugins/form_handlers/mail_sender/apps.py @@ -0,0 +1,15 @@ +from __future__ import absolute_import +from django.apps import AppConfig + +__title__ = 'fobi.contrib.plugins.form_handlers.mail_sender.apps' +__author__ = 'Artur Barseghyan ' +__copyright__ = '2014-2018 Artur Barseghyan' +__license__ = 'GPL 2.0/LGPL 2.1' +__all__ = ('Config',) + + +class Config(AppConfig): + """Config.""" + + name = 'fobi.contrib.plugins.form_handlers.mail_sender' + label = 'fobi_contrib_plugins_form_handlers_mail_sender' diff --git a/src/fobi/contrib/plugins/form_handlers/mail_sender/base.py b/src/fobi/contrib/plugins/form_handlers/mail_sender/base.py new file mode 100644 index 00000000..5040096b --- /dev/null +++ b/src/fobi/contrib/plugins/form_handlers/mail_sender/base.py @@ -0,0 +1,229 @@ +from __future__ import absolute_import + +import datetime +from mimetypes import guess_type +import os + +from six import string_types, PY3 + +from django.conf import settings +from django.template.loader import render_to_string +from django.utils.translation import ugettext_lazy as _ + +from .....base import ( + FormHandlerPlugin, + FormWizardHandlerPlugin, + get_processed_form_data, + get_processed_form_wizard_data, +) +from .....helpers import ( + safe_text, + extract_file_path, + get_form_element_entries_for_form_wizard_entry, +) + +from . import UID +from .forms import MailSenderForm +from .helpers import send_mail +from .mixins import MailSenderHandlerMixin +from .settings import MULTI_EMAIL_FIELD_VALUE_SPLITTER + +__title__ = 'fobi.contrib.plugins.form_handlers.mail_sender.base' +__author__ = 'Artur Barseghyan ' +__copyright__ = '2014-2018 Artur Barseghyan' +__license__ = 'GPL 2.0/LGPL 2.1' +__all__ = ( + 'MailSenderHandlerPlugin', + 'MailSenderWizardHandlerPlugin', +) + +# ***************************************************************************** +# **************************** Form handler *********************************** +# ***************************************************************************** + + +class MailSenderHandlerPlugin(FormHandlerPlugin, MailSenderHandlerMixin): + """Mail handler plugin. + + Sends emails to the person specified. Should be executed before + ``db_store`` and ``http_repost`` plugins. + """ + + uid = UID + name = _("Mail the sender") + form = MailSenderForm + + def run(self, form_entry, request, form, form_element_entries=None): + """Run. + + :param fobi.models.FormEntry form_entry: Instance of + ``fobi.models.FormEntry``. + :param django.http.HttpRequest request: + :param django.forms.Form form: + :param iterable form_element_entries: Iterable of + ``fobi.models.FormElementEntry`` objects. + """ + base_url = self.get_base_url(request) + + # Clean up the values, leave our content fields and empty values. + field_name_to_label_map, cleaned_data = get_processed_form_data( + form, + form_element_entries + ) + + rendered_data = self.get_rendered_data( + cleaned_data, + field_name_to_label_map, + base_url + ) + + files = self._prepare_files(request, form) + + self.send_email(rendered_data, cleaned_data, files) + + def plugin_data_repr(self): + """Human readable representation of plugin data. + + :return string: + """ + context = { + 'to_name': safe_text(self.data.to_name), + 'form_field_name_to_email': self.data.form_field_name_to_email, + 'subject': safe_text(self.data.subject), + } + return render_to_string('mail_sender/plugin_data_repr.html', context) + + +# ***************************************************************************** +# ************************ Form wizard handler ******************************** +# ***************************************************************************** + + +class MailSenderWizardHandlerPlugin(FormWizardHandlerPlugin): + """Mail wizard handler plugin. + + Sends emails to the person specified. Should be executed before + ``db_store`` and ``http_repost`` plugins. + """ + + uid = UID + name = _("Mail the sender") + form = MailSenderForm + + def run(self, form_wizard_entry, request, form_list, form_wizard, + form_element_entries=None): + """Run. + + :param fobi.models.FormWizardEntry form_wizard_entry: Instance + of :class:`fobi.models.FormWizardEntry`. + :param django.http.HttpRequest request: + :param list form_list: List of :class:`django.forms.Form` instances. + :param fobi.wizard.views.dynamic.DynamicWizardView form_wizard: + Instance of :class:`fobi.wizard.views.dynamic.DynamicWizardView`. + :param iterable form_element_entries: Iterable of + ``fobi.models.FormElementEntry`` objects. + """ + base_url = 'http{secure}://{host}'.format( + secure=('s' if request.is_secure() else ''), + host=request.get_host() + ) + + if not form_element_entries: + form_element_entries = \ + get_form_element_entries_for_form_wizard_entry( + form_wizard_entry + ) + + # Clean up the values, leave our content fields and empty values. + field_name_to_label_map, cleaned_data = get_processed_form_wizard_data( + form_wizard, + form_list, + form_element_entries + ) + + rendered_data = [] + for key, value in cleaned_data.items(): + if value and isinstance(value, string_types) \ + and value.startswith(settings.MEDIA_URL): + cleaned_data[key] = '{base_url}{value}'.format( + base_url=base_url, value=value + ) + label = field_name_to_label_map.get(key, key) + rendered_data.append('{0}: {1}\n'.format( + safe_text(label), safe_text(cleaned_data[key])) + ) + + files = self._prepare_files(request, form_list) + + to_email = cleaned_data.get(self.data.form_field_name_to_email) + # Handling more than one email address + if isinstance(to_email, (list, tuple)): + pass # Anything else needed here? + else: + # Assume that it's string + to_email = to_email.split( + MULTI_EMAIL_FIELD_VALUE_SPLITTER + ) + + send_mail( + safe_text(self.data.subject), + "{0}\n\n{1}".format( + safe_text(self.data.body), + ''.join(rendered_data) + ), + self.data.from_email, + to_email, + fail_silently=False, + attachments=files.values() + ) + + def _prepare_files(self, request, form_list): + """Prepares the files for being attached to the mail message.""" + files = {} + + def process_path(file_path, imf): + """Processes the file path and the file.""" + if file_path: + # if file_path.startswith(settings.MEDIA_URL): + # file_path = file_path[1:] + # file_path = settings.PROJECT_DIR('../{0}'.format(file_path)) + file_path = file_path.replace( + settings.MEDIA_URL, + os.path.join(settings.MEDIA_ROOT, '') + ) + mime_type = guess_type(imf.name) + if PY3: + imf_chunks = b''.join([c for c in imf.chunks()]) + else: + imf_chunks = ''.join([c for c in imf.chunks()]) + + files[field_name] = ( + imf.name, + imf_chunks, + mime_type[0] if mime_type else '' + ) + + for form in form_list: + for field_name, imf in request.FILES.items(): + try: + file_path = form.cleaned_data.get(field_name, '') + process_path(file_path, imf) + except Exception as err: + file_path = extract_file_path(imf.name) + process_path(file_path, imf) + + return files + + def plugin_data_repr(self): + """Human readable representation of plugin data. + + :return string: + """ + context = { + 'to_name': safe_text(self.data.to_name), + 'form_field_name_to_email': safe_text( + self.data.form_field_name_to_email + ), + 'subject': safe_text(self.data.subject), + } + return render_to_string('mail_sender/plugin_data_repr.html', context) diff --git a/src/fobi/contrib/plugins/form_handlers/mail_sender/conf.py b/src/fobi/contrib/plugins/form_handlers/mail_sender/conf.py new file mode 100644 index 00000000..f376da9d --- /dev/null +++ b/src/fobi/contrib/plugins/form_handlers/mail_sender/conf.py @@ -0,0 +1,30 @@ +from django.conf import settings + +from . import defaults + +__title__ = 'fobi.contrib.plugins.form_handlers.mail_sender.conf' +__author__ = 'Artur Barseghyan ' +__copyright__ = '2014-2018 Artur Barseghyan' +__license__ = 'GPL 2.0/LGPL 2.1' +__all__ = ('get_setting',) + + +def get_setting(setting, override=None): + """Get setting. + + Get a setting from ``fobi.contrib.plugins.form_handlers.mail`` conf + module, falling back to the default. + + If override is not None, it will be used instead of the setting. + + :param setting: String with setting name + :param override: Value to use when no setting is available. Defaults to + None. + :return: Setting value. + """ + if override is not None: + return override + if hasattr(settings, 'FOBI_PLUGIN_MAIL_SENDER_{0}'.format(setting)): + return getattr(settings, 'FOBI_PLUGIN_MAIL_SENDER_{0}'.format(setting)) + else: + return getattr(defaults, setting) diff --git a/src/fobi/contrib/plugins/form_handlers/mail_sender/defaults.py b/src/fobi/contrib/plugins/form_handlers/mail_sender/defaults.py new file mode 100644 index 00000000..fc42fb5a --- /dev/null +++ b/src/fobi/contrib/plugins/form_handlers/mail_sender/defaults.py @@ -0,0 +1,18 @@ +__title__ = 'fobi.contrib.plugins.form_handlers.mail_sender.defaults' +__author__ = 'Artur Barseghyan ' +__copyright__ = '2014-2018 Artur Barseghyan' +__license__ = 'GPL 2.0/LGPL 2.1' +__all__ = ( + 'AUTO_MAIL_BODY', + 'AUTO_MAIL_FROM', + 'AUTO_MAIL_SUBJECT', + 'AUTO_MAIL_TO', + 'MULTI_EMAIL_FIELD_VALUE_SPLITTER', +) + + +MULTI_EMAIL_FIELD_VALUE_SPLITTER = ',' # But can be '\n' +AUTO_MAIL_TO = [] +AUTO_MAIL_SUBJECT = 'Automatic email' +AUTO_MAIL_BODY = 'Automatic email' +AUTO_MAIL_FROM = '' diff --git a/src/fobi/contrib/plugins/form_handlers/mail_sender/fobi_form_handlers.py b/src/fobi/contrib/plugins/form_handlers/mail_sender/fobi_form_handlers.py new file mode 100644 index 00000000..718644fe --- /dev/null +++ b/src/fobi/contrib/plugins/form_handlers/mail_sender/fobi_form_handlers.py @@ -0,0 +1,18 @@ +from .....base import ( + form_handler_plugin_registry, + form_wizard_handler_plugin_registry, +) +from .base import MailSenderHandlerPlugin, MailSenderWizardHandlerPlugin + +__title__ = 'fobi.contrib.plugins.form_handlers.mail_sender.fobi_form_handlers' +__author__ = 'Artur Barseghyan ' +__copyright__ = '2014-2015 Artur Barseghyan' +__license__ = 'GPL 2.0/LGPL 2.1' +__all__ = ( + 'MailSenderHandlerPlugin', + 'MailSenderWizardHandlerPlugin', +) + + +form_handler_plugin_registry.register(MailSenderHandlerPlugin) +form_wizard_handler_plugin_registry.register(MailSenderWizardHandlerPlugin) diff --git a/src/fobi/contrib/plugins/form_handlers/mail_sender/forms.py b/src/fobi/contrib/plugins/form_handlers/mail_sender/forms.py new file mode 100644 index 00000000..0d9ff8c2 --- /dev/null +++ b/src/fobi/contrib/plugins/form_handlers/mail_sender/forms.py @@ -0,0 +1,71 @@ +from __future__ import absolute_import + +from django import forms +from django.utils.translation import ugettext_lazy as _ + +from .....base import BasePluginForm, get_theme + +__title__ = 'fobi.contrib.plugins.form_handlers.mail_sender.forms' +__author__ = 'Artur Barseghyan ' +__copyright__ = '2014-2018 Artur Barseghyan' +__license__ = 'GPL 2.0/LGPL 2.1' +__all__ = ('MailSenderForm',) + +theme = get_theme(request=None, as_instance=True) + + +class MailSenderForm(forms.Form, BasePluginForm): + """Form for ``BooleanSelectPlugin``.""" + + plugin_data_fields = [ + ("from_name", ""), + ("from_email", ""), + ("to_name", ""), + ("form_field_name_to_email", ""), + ("subject", ""), + ("body", ""), + ] + + from_name = forms.CharField( + label=_("From name"), + required=True, + widget=forms.widgets.TextInput( + attrs={'class': theme.form_element_html_class} + ) + ) + from_email = forms.EmailField( + label=_("From email"), + required=True, + widget=forms.widgets.TextInput( + attrs={'class': theme.form_element_html_class} + ) + ) + to_name = forms.CharField( + label=_("To name"), + required=True, + widget=forms.widgets.TextInput( + attrs={'class': theme.form_element_html_class} + ) + ) + form_field_name_to_email = forms.CharField( + label=_("Form field name to email"), + required=True, + help_text=_("Name of the form field to be used as email."), + widget=forms.widgets.TextInput( + attrs={'class': theme.form_element_html_class} + ) + ) + subject = forms.CharField( + label=_("Subject"), + required=True, + widget=forms.widgets.TextInput( + attrs={'class': theme.form_element_html_class} + ) + ) + body = forms.CharField( + label=_("Body"), + required=False, + widget=forms.widgets.Textarea( + attrs={'class': theme.form_element_html_class} + ) + ) diff --git a/src/fobi/contrib/plugins/form_handlers/mail_sender/helpers.py b/src/fobi/contrib/plugins/form_handlers/mail_sender/helpers.py new file mode 100644 index 00000000..35837180 --- /dev/null +++ b/src/fobi/contrib/plugins/form_handlers/mail_sender/helpers.py @@ -0,0 +1,36 @@ +from __future__ import absolute_import + +from django.core.mail import get_connection +from django.core.mail.message import EmailMultiAlternatives + +__title__ = 'fobi.contrib.plugins.form_handlers.mail_sender.helpers' +__author__ = 'Artur Barseghyan ' +__copyright__ = '2014-2018 Artur Barseghyan' +__license__ = 'GPL 2.0/LGPL 2.1' +__all__ = ('send_mail',) + + +def send_mail(subject, message, from_email, recipient_list, + fail_silently=False, auth_user=None, auth_password=None, + connection=None, html_message=None, attachments=None): + """Send email. + + Easy wrapper for sending a single message to a recipient list. All members + of the recipient list will see the other recipients in the 'To' field. + + If auth_user is None, the EMAIL_HOST_USER setting is used. + If auth_password is None, the EMAIL_HOST_PASSWORD setting is used. + + Note: The API for this method is frozen. New code wanting to extend the + functionality should use the EmailMessage class directly. + """ + connection = connection or get_connection(username=auth_user, + password=auth_password, + fail_silently=fail_silently) + mail = EmailMultiAlternatives(subject, message, from_email, recipient_list, + connection=connection, + attachments=attachments) + if html_message: + mail.attach_alternative(html_message, 'text/html') + + return mail.send() diff --git a/src/fobi/contrib/plugins/form_handlers/mail_sender/mixins.py b/src/fobi/contrib/plugins/form_handlers/mail_sender/mixins.py new file mode 100644 index 00000000..5a055454 --- /dev/null +++ b/src/fobi/contrib/plugins/form_handlers/mail_sender/mixins.py @@ -0,0 +1,131 @@ +from __future__ import absolute_import, unicode_literals + +import datetime +from mimetypes import guess_type +import os + +from six import string_types, PY3 + +from django.conf import settings + +from .....helpers import extract_file_path, safe_text + +from .helpers import send_mail +from .settings import MULTI_EMAIL_FIELD_VALUE_SPLITTER + +__title__ = 'fobi.contrib.plugins.form_handlers.mail_sender.mixins' +__author__ = 'Artur Barseghyan ' +__copyright__ = '2014-2018 Artur Barseghyan' +__license__ = 'GPL 2.0/LGPL 2.1' +__all__ = ( + 'MailSenderHandlerMixin', +) + +# ***************************************************************************** +# **************************** Form handler *********************************** +# ***************************************************************************** + + +class MailSenderHandlerMixin(object): + """Mail handler mixin.""" + + def get_base_url(self, request): + """Get base URL. + + Might be used in integration packages. + """ + base_url = 'http{secure}://{host}'.format( + secure=('s' if request.is_secure() else ''), + host=request.get_host() + ) + return base_url + + def get_rendered_data(self, + cleaned_data, + field_name_to_label_map, + base_url): + """Get rendered data. + + Might be used in integration packages. + """ + rendered_data = [] + for key, value in cleaned_data.items(): + if value: + if isinstance(value, string_types) \ + and value.startswith(settings.MEDIA_URL): + cleaned_data[key] = '{base_url}{value}'.format( + base_url=base_url, value=value + ) + + if isinstance(value, (datetime.datetime, datetime.date)): + cleaned_data[key] = value.isoformat() \ + if hasattr(value, 'isoformat') \ + else value + + label = field_name_to_label_map.get(key, key) + rendered_data.append('{0}: {1}\n'.format( + safe_text(label), safe_text(cleaned_data[key])) + ) + return rendered_data + + def send_email(self, rendered_data, cleaned_data, files): + """Send email. + + Might be used in integration packages. + """ + to_email = cleaned_data.get(self.data.form_field_name_to_email) + # Handling more than one email address + if isinstance(to_email, (list, tuple)): + pass # Anything else needed here? + else: + # Assume that it's string + to_email = to_email.split( + MULTI_EMAIL_FIELD_VALUE_SPLITTER + ) + + send_mail( + safe_text(self.data.subject), + u"{0}\n\n{1}".format( + safe_text(self.data.body), + ''.join(rendered_data) + ), + self.data.from_email, + to_email, + fail_silently=False, + attachments=files.values() + ) + + def _prepare_files(self, request, form): + """Prepares the files for being attached to the mail message.""" + files = {} + + def process_path(file_path, imf): + """Processes the file path and the file.""" + if file_path: + # if file_path.startswith(settings.MEDIA_URL): + # file_path = file_path[1:] + # file_path = settings.PROJECT_DIR('../{0}'.format(file_path)) + file_path = file_path.replace( + settings.MEDIA_URL, + os.path.join(settings.MEDIA_ROOT, '') + ) + mime_type = guess_type(imf.name) + if PY3: + imf_chunks = b''.join([c for c in imf.chunks()]) + else: + imf_chunks = str('').join([c for c in imf.chunks()]) + files[field_name] = ( + imf.name, + imf_chunks, + mime_type[0] if mime_type else '' + ) + + for field_name, imf in request.FILES.items(): + try: + file_path = form.cleaned_data.get(field_name, '') + process_path(file_path, imf) + except Exception as err: + file_path = extract_file_path(imf.name) + process_path(file_path, imf) + + return files diff --git a/src/fobi/contrib/plugins/form_handlers/mail_sender/settings.py b/src/fobi/contrib/plugins/form_handlers/mail_sender/settings.py new file mode 100644 index 00000000..45fe3215 --- /dev/null +++ b/src/fobi/contrib/plugins/form_handlers/mail_sender/settings.py @@ -0,0 +1,13 @@ +from .conf import get_setting + +__title__ = 'fobi.contrib.plugins.form_handlers.mail_sender.settings' +__author__ = 'Artur Barseghyan ' +__copyright__ = '2014-2018 Artur Barseghyan' +__license__ = 'GPL 2.0/LGPL 2.1' +__all__ = ( + 'MULTI_EMAIL_FIELD_VALUE_SPLITTER', +) + +MULTI_EMAIL_FIELD_VALUE_SPLITTER = get_setting( + 'MULTI_EMAIL_FIELD_VALUE_SPLITTER' +) diff --git a/src/fobi/contrib/plugins/form_handlers/mail_sender/templates/mail_sender/plugin_data_repr.html b/src/fobi/contrib/plugins/form_handlers/mail_sender/templates/mail_sender/plugin_data_repr.html new file mode 100644 index 00000000..36d235d5 --- /dev/null +++ b/src/fobi/contrib/plugins/form_handlers/mail_sender/templates/mail_sender/plugin_data_repr.html @@ -0,0 +1,3 @@ +{% load i18n %} +

{% trans "To" %}: Form field name to email: "{{ form_field_name_to_email|striptags }}"

+

{% trans "Subject" %}: {{ subject|striptags }}

diff --git a/src/fobi/helpers.py b/src/fobi/helpers.py index e6582ba1..e0690152 100644 --- a/src/fobi/helpers.py +++ b/src/fobi/helpers.py @@ -62,6 +62,7 @@ __all__ = ( 'do_slugify', 'empty_string', 'ensure_unique_filename', + 'extract_file_path', 'flatatt_inverse_quotes', 'get_app_label_and_model_name', 'get_form_element_entries_for_form_wizard_entry', diff --git a/src/fobi/test.py b/src/fobi/test.py deleted file mode 100644 index c3e47511..00000000 --- a/src/fobi/test.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import print_function - -import unittest - -from .tests.test_browser_build_dynamic_forms import * -from .tests.test_core import * -from .tests.test_dynamic_forms import * -from .tests.test_sortable_dict import * - -__title__ = 'fobi.test' -__author__ = 'Artur Barseghyan ' -__copyright__ = '2014-2018 Artur Barseghyan' -__license__ = 'GPL 2.0/LGPL 2.1' - - -if __name__ == '__main__': - unittest.main() diff --git a/src/fobi/tests/data.py b/src/fobi/tests/data.py index 41816674..7d24b1a6 100644 --- a/src/fobi/tests/data.py +++ b/src/fobi/tests/data.py @@ -68,6 +68,8 @@ from fobi.contrib.plugins.form_handlers \ .db_store.fobi_form_handlers import DBStoreHandlerPlugin from fobi.contrib.plugins.form_handlers \ .mail.fobi_form_handlers import MailHandlerPlugin +from fobi.contrib.plugins.form_handlers \ + .mail_sender.fobi_form_handlers import MailSenderHandlerPlugin from fobi.contrib.plugins.form_handlers \ .http_repost.fobi_form_handlers import HTTPRepostHandlerPlugin @@ -96,13 +98,6 @@ TEST_FORM_ELEMENT_PLUGIN_DATA = { 'required': False, }, - # Add a "Select multiple" (select multiple input) form elelement - # force_text(CheckboxSelectMultipleInputPlugin.name): { - # 'label': "Test checkbox select multiple input", - # 'help_text': "Lorem ipsum select multiple input", - # 'required': False, - # }, - # Add a "Date" input form element force_text(DateInputPlugin.name): { 'label': "Test date input", @@ -292,6 +287,14 @@ TEST_FORM_HANDLER_PLUGIN_DATA = { 'subject': "Test email subject", 'body': "Test email body", }, + force_text(MailSenderHandlerPlugin.name): { + 'from_name': "From me", + 'from_email': "from@example.com", + 'to_name': "To you", + 'form_field_name_to_email': "test_email_input", + 'subject': "Test email subject", + 'body': "Test email body", + }, force_text(HTTPRepostHandlerPlugin.name): { 'endpoint_url': 'http://dev.example.com' } diff --git a/src/fobi/tests/test_browser_build_dynamic_forms.py b/src/fobi/tests/test_browser_build_dynamic_forms.py index 9a2cc691..908e282e 100644 --- a/src/fobi/tests/test_browser_build_dynamic_forms.py +++ b/src/fobi/tests/test_browser_build_dynamic_forms.py @@ -502,6 +502,7 @@ class FobiBrowserBuldDynamicFormsTest(BaseFobiBrowserBuldDynamicFormsTest): # Add form elements self._test_add_form_elements(create_form=True) + self._test_add_form_handlers(create_form=False) # self._sleep(wait)