PKOfIn3~  plugs_mail/mail.py""" Plugs Mail """ import logging from post_office import mail from post_office.models import EmailTemplate from plugs_core import utils from plugs_mail.settings import app_settings LOGGER = logging.getLogger(__name__) class PlugsMail(object): """ Solo mail is the class responsible for getting and validating the context and prepare the email for sending """ template = None context = None context_data = {} data = None def __init__(self): self.validate_context() assert self.template, 'Must set template attribute on subclass.' def validate_context(self): """ Make sure there are no duplicate context objects or we might end up with switched data Converting the tuple to a set gets rid of the eventual duplicate objects, comparing the length of the original tuple and set tells us if we have duplicates in the tuple or not """ if self.context and len(self.context) != len(set(self.context)): LOGGER.error('Cannot have duplicated context objects') raise Exception('Cannot have duplicated context objects.') def get_instance_of(self, model_cls): """ Search the data to find a instance of a model specified in the template """ for obj in self.data.values(): if isinstance(obj, model_cls): return obj LOGGER.error('Context Not Found') raise Exception('Context Not Found') def get_context(self): """ Create a dict with the context data context is not required, but if it is defined it should be a tuple """ if not self.context: return else: assert isinstance(self.context, tuple), 'Expected a Tuple not {0}'.format(type(self.context)) for model in self.context: model_cls = utils.get_model_class(model) key = utils.camel_to_snake(model_cls.__name__) self.context_data[key] = self.get_instance_of(model_cls) def get_extra_context(self): """ Override this method if you want to provide extra context. The extra_context must be a dict. Be very careful no validation is being performed. """ return {} def get_context_data(self): """ Context Data is equal to context + extra_context Merge the dicts context_data and extra_context and update state """ self.get_context() self.context_data.update(self.get_extra_context()) return self.context_data def send(self, to, language=None, **data): """ This is the method to be called """ self.data = data self.get_context_data() if app_settings['SEND_EMAILS']: try: if language: mail.send(to, template=self.template, context=self.context_data, language=language) else: mail.send(to, template=self.template, context=self.context_data) except EmailTemplate.DoesNotExist: msg = 'Trying to use a non existent email template {0}'.format(self.template) LOGGER.error('Trying to use a non existent email template {0}'.format(self.template)) PK30J=Pooplugs_mail/utils.py""" Plugs Mail Utils """ from django.utils import translation from django.contrib.auth import get_user_model def to_email(email_class, email, language=None, **data): """ Send email to specified email address """ if language: email_class().send([email], language=language, **data) else: email_class().send([email], translation.get_language(), **data) def to_user(email_class, user, **data): """ Email user """ try: email_class().send([user.email], user.language, **data) except AttributeError: # this is a fallback in case the user model does not have the language field email_class().send([user.email], translation.get_language(), **data) def to_staff(email_class, **data): """ Email staff users """ for user in get_user_model().objects.filter(is_staff=True): try: email_class().send([user.email], user.language, **data) except AttributeError: email_class().send([user.email], translation.get_language(), **data) def to_superuser(email_class, **data): """ Email superusers """ for user in get_user_model().objects.filter(is_superuser=True): try: email_class().send([user.email], user.language, **data) except AttributeError: email_class().send([user.email], translation.get_language(), **data) PK;0JiȺplugs_mail/__init__.py__version__ = '0.1.5' PK͈IC7((plugs_mail/settings.py""" Plugs Mail Settings """ from django.conf import settings from django.core.exceptions import ImproperlyConfigured MANDATORY_SETTINGS = ['SEND_EMAILS'] PROJECT_SETTINGS = getattr(settings, 'PLUGS_MAIL', {}) for setting in MANDATORY_SETTINGS: try: PROJECT_SETTINGS[setting] except KeyError: raise ImproperlyConfigured('Missing setting: PLUGS_MAIL[\'{0}\']'.format(setting)) # Default Settings PROJECT_SETTINGS['OVERRIDE_TEMPLATE_DIR'] = PROJECT_SETTINGS.get('OVERRIDE_TEMPLATE_DIR', None) app_settings = PROJECT_SETTINGS PKI#plugs_mail/templatetags/__init__.pyPKlIg""$plugs_mail/templatetags/emailtags.pyfrom django import template from django.utils.safestring import mark_safe register = template.Library() def get_styles(): """ Return inline css styles """ style = "background: #3f51b5;" \ "display: block;" \ "font-size: 13px;" \ "font-weight: 100;" \ "font-family: Helvetica,Arial,sans-serif;" \ "text-transform: uppercase;" \ "text-align: center;" \ "letter-spacing: 1px;" \ "text-decoration: none;" \ "margin: 0 auto;" \ "line-height: 62px;" \ "width: 300px;" \ "height: 60px;" \ "-webkit-border-radius: 3px;" \ "-moz-border-radius: 3px;" \ "border-radius: 3px;" \ "color: #ffffff;" \ "webkit-text-size-adjust: none;" \ "mso-hide: all;" return style @register.simple_tag def button(url, txt): template = '
{2}
' html = template.format(url, get_styles(), txt) return mark_safe(html) PKI!plugs_mail/management/__init__.pyPKvIJ4,,6plugs_mail/management/commands/load_email_templates.pyimport os import inspect from pathlib import Path from django.conf import settings from django.core.management.base import BaseCommand from django.utils.module_loading import import_module from post_office import models from plugs_core import utils from plugs_mail.mail import PlugsMail from plugs_mail.settings import app_settings as plugs_mail_settings class Command(BaseCommand): overrides = {} def handle(self, *args, **options): self.override_default_templates() templates = self.get_apps() count = self.create_templates(templates) if count: self.stdout.write(self.style.SUCCESS('Successfully loaded %s email templates' % count)) else: self.stdout.write(self.style.SUCCESS('No email templates to load')) def override_default_templates(self): """ Override the default emails already defined by other apps """ if plugs_mail_settings['OVERRIDE_TEMPLATE_DIR']: dir_ = plugs_mail_settings['OVERRIDE_TEMPLATE_DIR'] for file_ in os.listdir(dir_): if file_.endswith(('.html', 'txt')): self.overrides[file_] = dir_ def get_apps(self): """ Get the list of installed apps and return the apps that have an emails module """ templates = [] for app in settings.INSTALLED_APPS: try: app = import_module(app + '.emails') templates += self.get_plugs_mail_classes(app) except ImportError: pass return templates def get_members(self, app): return inspect.getmembers(app) def get_templates_files_in_dir(self, dir_): return os.listdir(dir_) def get_template_files(self, location, class_name): """ Multilanguage support means that for each template we can have multiple templtate files, this methods returns all the template (html and txt) files that match the (class) template name """ template_name = utils.camel_to_snake(class_name) dir_ = location[:-9] + 'templates/emails/' files_ = [] for file_ in self.get_templates_files_in_dir(dir_): if file_.startswith(template_name) and file_.endswith(('.html', '.txt')): if file_ in self.overrides: files_.append(self.overrides[file_] + file_) else: files_.append(dir_ + file_) return files_ def get_plugs_mail_classes(self, app): """ Returns a list of tuples, but it should return a list of dicts """ classes = [] members = self.get_members(app) for member in members: name, cls = member if inspect.isclass(cls) and issubclass(cls, PlugsMail) and name != 'PlugsMail': files_ = self.get_template_files(app.__file__, name) for file_ in files_: try: description = cls.description location = file_ language = self.get_template_language(location) classes.append((name, location, description, language)) except AttributeError: raise AttributeError('Email class must specify email description.') return classes def get_template_language(self, file_): """ Return the template language Every template file must end in with the language code, and the code must match the ISO_6301 lang code https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes valid examples: account_created_pt.html payment_created_en.txt """ stem = Path(file_).stem language_code = stem.split('_')[-1:][0] if len(language_code) != 2: # TODO naive and temp implementation # check if the two chars correspond to one of the # available languages raise Exception('Template file `%s` must end in ISO_639-1 language code.' % file_) return language_code.lower() def get_subject(self, text): """ Email template subject is the first line of the email template, we can optionally add SUBJECT: to make it clearer """ first_line = text.splitlines(True)[0] # TODO second line should be empty if first_line.startswith('SUBJECT:'): subject = first_line[len('SUBJECT:'):] else: subject = first_line return subject.strip() def get_html_content(self, text): """ Parse content and return html """ lines = text.splitlines(True) return ''.join(lines[2:]) def create_templates(self, templates): """ Gets a list of templates to insert into the database """ count = 0 for template in templates: if not self.template_exists_db(template): name, location, description, language = template text = self.open_file(location) html_content = self.get_html_content(text) data = { 'name': utils.camel_to_snake(name).upper(), 'html_content': html_content, 'content': self.text_version(html_content), 'subject': self.get_subject(text), 'description': description, 'language': language } if models.EmailTemplate.objects.create(**data): count += 1 return count def text_version(self, html): """ Uses util to create a text email template from a html one """ return utils.html_to_text(html) def open_file(self, file_): """ Receives a file path has input and returns a string with the contents of the file """ with open(file_, 'r', encoding='utf-8') as file: text = '' for line in file: text += line return text def template_exists_db(self, template): """ Receives a template and checks if it exists in the database using the template name and language """ name = utils.camel_to_snake(template[0]).upper() language = utils.camel_to_snake(template[3]) try: models.EmailTemplate.objects.get(name=name, language=language) except models.EmailTemplate.DoesNotExist: return False return True PKI*plugs_mail/management/commands/__init__.pyPKI]]plugs_mail/templates/base.html {% block content %}{% endblock %} PKI plugs_mail/templates/__init__.pyPK0JDq*plugs_mail-0.1.5.dist-info/DESCRIPTION.rst===== Plugs Mail ===== .. image:: https://badge.fury.io/py/plugs-mail.svg :target: https://badge.fury.io/py/plugs-mail Quick start ----------- Soon... History ------- 0.1.5 (2017-01-16) ++++++++++++++++++ * First release on PyPI. PK0Jϯ(plugs_mail-0.1.5.dist-info/metadata.json{"classifiers": ["Development Status :: 3 - Alpha", "Framework :: Django", "Framework :: Django :: 1.9", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Natural Language :: English", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5"], "extensions": {"python.details": {"contacts": [{"email": "ricardolobo@soloweb.pt", "name": "Ricardo Lobo", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/ricardolobo/plugs-mail"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "keywords": ["plugs-mail"], "license": "MIT", "metadata_version": "2.0", "name": "plugs-mail", "run_requires": [{"requires": ["cssselect (>=1.0.0)", "django-post-office (>=2.0.7)", "plugs-core (>=0.1.0)"]}], "summary": "Your project description goes here", "version": "0.1.5"}PK0JF (plugs_mail-0.1.5.dist-info/top_level.txtplugs_mail PK0Jndnn plugs_mail-0.1.5.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any PK0J_?9^^#plugs_mail-0.1.5.dist-info/METADATAMetadata-Version: 2.0 Name: plugs-mail Version: 0.1.5 Summary: Your project description goes here Home-page: https://github.com/ricardolobo/plugs-mail Author: Ricardo Lobo Author-email: ricardolobo@soloweb.pt License: MIT Keywords: plugs-mail Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Framework :: Django Classifier: Framework :: Django :: 1.9 Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Natural Language :: English Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Requires-Dist: cssselect (>=1.0.0) Requires-Dist: django-post-office (>=2.0.7) Requires-Dist: plugs-core (>=0.1.0) ===== Plugs Mail ===== .. image:: https://badge.fury.io/py/plugs-mail.svg :target: https://badge.fury.io/py/plugs-mail Quick start ----------- Soon... History ------- 0.1.5 (2017-01-16) ++++++++++++++++++ * First release on PyPI. PK0JPf!plugs_mail-0.1.5.dist-info/RECORDplugs_mail/__init__.py,sha256=DQiXHqQ3C29f_--ye7aVMDPnITrl9M0zMU42LYY48zE,22 plugs_mail/mail.py,sha256=9wyiW-ATkrC3Io-gYaaEJhma_VBQ6e6YFo-w6_WT2kA,3355 plugs_mail/settings.py,sha256=-DREj-H7_c8FPnRO4ogZCG_ZI1hgFCaUo2pDeyYntIE,552 plugs_mail/utils.py,sha256=6JRr90rwLX-eDjJ_98O3eljrs6ESRRLWe4CSOk1qI_w,1391 plugs_mail/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 plugs_mail/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 plugs_mail/management/commands/load_email_templates.py,sha256=rgV8K7vYvEAPk5ywHz8fz8wysQ0vjw09W8s3ZmaGj5A,6700 plugs_mail/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 plugs_mail/templates/base.html,sha256=ADDgAdk5lwZwiHGx1V704yTuRsEFgjANAETfxEqTkgk,93 plugs_mail/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 plugs_mail/templatetags/emailtags.py,sha256=iY-wSSmsPd1NwCzan1z30i2AN9bba4D0C1XRShGZTGA,1058 plugs_mail-0.1.5.dist-info/DESCRIPTION.rst,sha256=NzRN4oGuCtoRa2uKfoOVsqtB7UkBI4UjIG-yRnrSoBw,245 plugs_mail-0.1.5.dist-info/METADATA,sha256=Htjwx9QpKmCDMpaIs3t_a-4aA0Rb1XOgAGcmI21cgj0,1118 plugs_mail-0.1.5.dist-info/RECORD,, plugs_mail-0.1.5.dist-info/WHEEL,sha256=GrqQvamwgBV4nLoJe0vhYRSWzWsx7xjlt74FT0SWYfE,110 plugs_mail-0.1.5.dist-info/metadata.json,sha256=bw8svLlnl0Dm0HpFzIcAKcJnQ6CReC6AhY0HksN9KQY,996 plugs_mail-0.1.5.dist-info/top_level.txt,sha256=b-GIaf_mNMrsnm8QoGa8Kp84PTmY0DuFyGi37tTaOZw,11 PKOfIn3~  plugs_mail/mail.pyPK30J=PooK plugs_mail/utils.pyPK;0JiȺplugs_mail/__init__.pyPK͈IC7((5plugs_mail/settings.pyPKI#plugs_mail/templatetags/__init__.pyPKlIg""$plugs_mail/templatetags/emailtags.pyPKI!6plugs_mail/management/__init__.pyPKvIJ4,,6uplugs_mail/management/commands/load_email_templates.pyPKI*4plugs_mail/management/commands/__init__.pyPKI]]=5plugs_mail/templates/base.htmlPKI 5plugs_mail/templates/__init__.pyPK0JDq*6plugs_mail-0.1.5.dist-info/DESCRIPTION.rstPK0Jϯ(Q7plugs_mail-0.1.5.dist-info/metadata.jsonPK0JF ({;plugs_mail-0.1.5.dist-info/top_level.txtPK0Jndnn ;plugs_mail-0.1.5.dist-info/WHEELPK0J_?9^^#x<plugs_mail-0.1.5.dist-info/METADATAPK0JPf!Aplugs_mail-0.1.5.dist-info/RECORDPKCG