def test_parse_style_rules(): p = Premailer('html') # won't need the html func = p._parse_style_rules rules, leftover = func(""" h1, h2 { color:red; } /* ignore this */ strong { text-decoration:none } ul li { list-style: 2px; } a:hover { text-decoration: underline } """) # 'rules' is a list, turn it into a dict for # easier assertion testing rules_dict = {} for k, v in rules: rules_dict[k] = v assert 'h1' in rules_dict assert 'h2' in rules_dict assert 'strong' in rules_dict assert 'ul li' in rules_dict # order is important rules_keys = [x[0] for x in rules] assert rules_keys.index('h1') < rules_keys.index('h2') assert rules_keys.index('strong') < rules_keys.index('ul li') assert rules_dict['h1'] == 'color:red' assert rules_dict['h2'] == 'color:red' assert rules_dict['strong'] == 'text-decoration:none' assert rules_dict['ul li'] == 'list-style:2px' assert rules_dict['a:hover'] == 'text-decoration:underline' p = Premailer('html', exclude_pseudoclasses=True) # won't need the html func = p._parse_style_rules rules, leftover = func(""" ul li { list-style: 2px; } a:hover { text-decoration: underline } """) assert len(rules) == 1 k, v = rules[0] assert k == 'ul li' assert v == 'list-style:2px' assert len(leftover) == 1 k, v = leftover[0] assert (k, v) == ('a:hover', 'text-decoration:underline'), (k, v)
def test_parse_style_rules(self): p = Premailer('html') # won't need the html func = p._parse_style_rules rules, leftover = func( """ h1, h2 { color:red; } /* ignore this */ strong { text-decoration:none } ul li { list-style: none; } a:hover { text-decoration: underline } """, 0) # 'rules' is a list, turn it into a dict for # easier assertion testing rules_dict = {} rules_specificity = {} for specificity, k, v in rules: rules_dict[k] = v rules_specificity[k] = specificity ok_('h1' in rules_dict) ok_('h2' in rules_dict) ok_('strong' in rules_dict) ok_('ul li' in rules_dict) eq_(rules_dict['h1'], 'color:red') eq_(rules_dict['h2'], 'color:red') eq_(rules_dict['strong'], 'text-decoration:none') eq_(rules_dict['ul li'], 'list-style:none') ok_('a:hover' not in rules_dict) p = Premailer('html', exclude_pseudoclasses=True) # won't need the html func = p._parse_style_rules rules, leftover = func( """ ul li { list-style: none; } a:hover { text-decoration: underline } """, 0) eq_(len(rules), 1) specificity, k, v = rules[0] eq_(k, 'ul li') eq_(v, 'list-style:none') eq_(len(leftover), 1) k, v = leftover[0] eq_((k, v), ('a:hover', 'text-decoration:underline'), (k, v))
def test_parse_style_rules(): p = Premailer('html') # won't need the html func = p._parse_style_rules rules, leftover = func( """ h1, h2 { color:red; } /* ignore this */ strong { text-decoration:none } ul li { list-style: 2px; } a:hover { text-decoration: underline } """, 0) # 'rules' is a list, turn it into a dict for # easier assertion testing rules_dict = {} rules_specificity = {} for specificity, k, v in rules: rules_dict[k] = v rules_specificity[k] = specificity assert 'h1' in rules_dict assert 'h2' in rules_dict assert 'strong' in rules_dict assert 'ul li' in rules_dict assert rules_dict['h1'] == 'color:red' assert rules_dict['h2'] == 'color:red' assert rules_dict['strong'] == 'text-decoration:none' assert rules_dict['ul li'] == 'list-style:2px' assert 'a:hover' not in rules_dict p = Premailer('html', exclude_pseudoclasses=True) # won't need the html func = p._parse_style_rules rules, leftover = func( """ ul li { list-style: 2px; } a:hover { text-decoration: underline } """, 0) assert len(rules) == 1 specificity, k, v = rules[0] assert k == 'ul li' assert v == 'list-style:2px' assert len(leftover) == 1 k, v = leftover[0] assert (k, v) == ('a:hover', 'text-decoration:underline'), (k, v)
def test_last_child_exclude_pseudo(): html = """<html> <head> <style type="text/css"> div { text-align: right; } div:last-child { text-align: left; } </style> </head> <body> <div>First child</div> <div>Last child</div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="text-align:right" align="right">First child</div> <div style="text-align:left" align="left">Last child</div> </body> </html>""" p = Premailer(html, exclude_pseudoclasses=True) result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_mediaquery(): html = """<html> <head> <style type="text/css"> div { text-align: right; } @media print{ div { text-align: center; } } </style> </head> <body> <div>First div</div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="text-align:right" align="right">First div</div> </body> </html>""" p = Premailer(html) result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def inline_template(template_source_name: str) -> None: template_name = template_source_name.split('.source.html')[0] template_path = os.path.join(EMAIL_TEMPLATES_PATH, template_source_name) compiled_template_path = os.path.join( os.path.dirname(template_path), "compiled", os.path.basename(template_name) + ".html") os.makedirs(os.path.dirname(compiled_template_path), exist_ok=True) with open(template_path) as template_source_file: template_str = template_source_file.read() output = Premailer(template_str, external_styles=[CSS_SOURCE_PATH]).transform() output = escape_jinja2_characters(output) # Premailer.transform will try to complete the DOM tree, # adding html, head, and body tags if they aren't there. # While this is correct for the email_base_default template, # it is wrong for the other templates that extend this # template, since we'll end up with 2 copipes of those tags. # Thus, we strip this stuff out if the template extends # another template. if template_name not in ['email_base_default', 'macros']: output = strip_unnecesary_tags(output) if ('zerver/emails/compiled/email_base_default.html' in output or 'zerver/emails/email_base_messages.html' in output): assert output.count('<html>') == 0 assert output.count('<body>') == 0 assert output.count('</html>') == 0 assert output.count('</body>') == 0 with open(compiled_template_path, 'w') as final_template_file: final_template_file.write(output)
def test_last_child_exclude_pseudo(self): html = """<html> <head> <style type="text/css"> div { text-align: right; } div:last-child { text-align: left; } </style> </head> <body> <div>First child</div> <div>Last child</div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="text-align:right" align="right">First child</div> <div style="text-align:left" align="left">Last child</div> </body> </html>""" p = Premailer(html, exclude_pseudoclasses=True) result_html = p.transform() compare_html(expect_html, result_html)
def test_child_selector(): html = """<html> <head> <style type="text/css"> body > div { text-align: right; } </style> </head> <body> <div>First div</div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="text-align:right" align="right">First div</div> </body> </html>""" p = Premailer(html) result_html = p.transform() whitespace_between_tags = re.compile('>\s*<', ) expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_prefer_inline_to_class(): html = """<html> <head> <style> .example { color: black; } </style> </head> <body> <div class="example" style="color:red"></div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="color:red"></div> </body> </html>""" p = Premailer(html) result_html = p.transform() whitespace_between_tags = re.compile('>\s*<', ) expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_precedence_comparison(): p = Premailer('html') # won't need the html rules, leftover = p._parse_style_rules( """ #identified { color:blue; } h1, h2 { color:red; } ul li { list-style: 2px; } li.example { color:green; } strong { text-decoration:none } div li.example p.sample { color:black; } """, 0) # 'rules' is a list, turn it into a dict for # easier assertion testing rules_specificity = {} for specificity, k, v in rules: rules_specificity[k] = specificity # Last in file wins assert rules_specificity['h1'] < rules_specificity['h2'] # More elements wins assert rules_specificity['strong'] < rules_specificity['ul li'] # IDs trump everything assert (rules_specificity['div li.example p.sample'] < rules_specificity['#identified']) # Classes trump multiple elements assert (rules_specificity['ul li'] < rules_specificity['li.example'])
def test_doctype(self): html = ( '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ' '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' """<html> <head> </head> <body> </body> </html>""") expect_html = ( '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ' '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' """<html> <head> </head> <body> </body> </html>""") p = Premailer(html) result_html = p.transform() compare_html(expect_html, result_html)
def send(request, id, test=False): newsletter = Newsletter.objects.get(id=id) subject = newsletter.name pm = Premailer( _newsletter_html(request, newsletter), base_url=cyc_settings.CYCLOPE_BASE_URL, preserve_internal_links=True, ) html_message = pm.transform() if test: recipients = map(strip, newsletter.test_recipients.split(',')) else: recipients = map(strip, newsletter.recipients.split(',')) if newsletter.sender_name: from_address = "%s <%s>" % (newsletter.sender_name, newsletter.sender) else: from_address = newsletter.sender msg = EmailMessage(subject, html_message, from_address, recipients) msg.content_subtype = "html" try: msg.send() except: logger = logging.getLogger("django.request") logger.exception("Newsletter send failed") return HttpResponseRedirect(reverse('newsletter_failed', args=[id])) else: return HttpResponseRedirect(reverse('newsletter_sent', args=[id]))
def sendtest(self, request, campaign_id): from premailer import Premailer from django.core.mail import send_mail, BadHeaderError receiver = request.POST.get('receiver') if not receiver: raise Http404 try: campaign = self.model.objects.get(pk=campaign_id) except self.model.DoesNotExist: pass else: content = campaign.render_html(request, scheme='http://', test=True) content = Premailer(content, strip_important=False).transform() plain = campaign.render_plain(request, test=True) try: send_mail(campaign.subject, plain, settings.DEFAULT_FROM_EMAIL, recipient_list=[receiver], html_message=content) except BadHeaderError: pass response = JsonResponse({}) set_cookie(response, 'last_receiver', receiver) return response
def make_search_email(search_bookmark): msg = make_email_with_campaign(search_bookmark, 'analyse-alerts') html_email = get_template('bookmarks/email_for_searches.html') parsed_url = urlparse.urlparse(search_bookmark.dashboard_url()) if parsed_url.query: qs = '?' + parsed_url.query + '&' + msg.qs else: qs = '?' + msg.qs dashboard_uri = (settings.GRAB_HOST + parsed_url.path + qs + '#' + parsed_url.fragment) with NamedTemporaryFile(suffix='.png') as graph_file: graph = attach_image(msg, search_bookmark.dashboard_url(), graph_file.name, '#results .tab-pane.active') unsubscribe_link = settings.GRAB_HOST + reverse( 'bookmark-login', kwargs={'key': search_bookmark.user.profile.key}) html = html_email.render( context={ 'bookmark': search_bookmark, 'domain': settings.GRAB_HOST, 'graph': graph, 'dashboard_uri': mark_safe(dashboard_uri), 'unsubscribe_link': unsubscribe_link }) html = Premailer(html, cssutils_logging_level=logging.ERROR).transform() html = unescape_href(html) text = email_as_text(html) msg.body = text msg.attach_alternative(html, "text/html") msg.extra_headers['list-unsubscribe'] = "<%s>" % unsubscribe_link msg.tags = ["monthly_update", "analyse"] return msg
def test_favour_rule_with_class_over_generic(self): html = """<html> <head> <style> div.example { color: green; } div { color: black; } </style> </head> <body> <div class="example"></div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="color:green"></div> </body> </html>""" p = Premailer(html) result_html = p.transform() compare_html(expect_html, result_html)
def test_prefer_inline_to_class(self): html = """<html> <head> <style> .example { color: black; } </style> </head> <body> <div class="example" style="color:red"></div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="color:red"></div> </body> </html>""" p = Premailer(html) result_html = p.transform() compare_html(expect_html, result_html)
def __call__(self, *args, **kw): '''Render the page, and then run it through :mod:`premailer` :param list args: The arguments to the page. :param dict kw: The keyword-arguments to the page. This method calls the __call__ of the super-class with the ``args`` and ``kw``. If the output is HTML it: * Turns the HTML ``<style>`` elements into ``style`` attributtes by calling :func:`premailer.transform`, and * Removes the unused HTML ``<style>`` elements. This allows the HTML to be rendered consistently in email-clients.''' orig = super(SiteEmail, self).__call__(*args, **kw) if orig[0] == '<': # --=mpj17=-- This is probabily markup, so tidy it some. premailer = Premailer( orig, preserve_internal_links=True, keep_style_tags=False, remove_classes=False, strip_important=False, disable_validation=True) premailed = premailer.transform() enlongened = self.fix_color_codes(premailed) retval = to_unicode_or_bust(enlongened) if retval[:9] != '<!DOCTYPE': retval = '<!DOCTYPE html>\n' + retval else: # --=mpj17=-- This is probabily plain-text, so just return it. retval = orig return retval
def test_strip_important(): """Get rid of !important. Makes no sense inline.""" html = """<html> <head> <style type="text/css"> p { height:100% !important; width:100% !important; } </style> </head> <body> <p>Paragraph</p> </body> </html> """ expect_html = """<html> <head> </head> <body> <p style="height:100%; width:100%" width="100%" height="100%">Paragraph</p> </body> </html>""" p = Premailer(html, strip_important=True) result_html = p.transform() whitespace_between_tags = re.compile('>\s*<', ) expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_favour_rule_with_id_over_others(): html = """<html> <head> <style> #identified { color: green; } div.example { color: black; } </style> </head> <body> <div class="example" id="identified"></div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div id="identified" style="color:green"></div> </body> </html>""" p = Premailer(html) result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_child_selector(self): html = """<html> <head> <style type="text/css"> body > div { text-align: right; } </style> </head> <body> <div>First div</div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="text-align:right" align="right">First div</div> </body> </html>""" p = Premailer(html) result_html = p.transform() compare_html(expect_html, result_html)
def test_mailto_url(): """if you use URL with mailto: protocol, they should stay as mailto: when baseurl is used """ if not etree: # can't test it return html = """<html> <head> <title>Title</title> </head> <body> <a href="mailto:[email protected]">[email protected]</a> </body> </html>""" expect_html = """<html> <head> <title>Title</title> </head> <body> <a href="mailto:[email protected]">[email protected]</a> </body> </html>""" p = Premailer(html, base_url='http://kungfupeople.com') result_html = p.transform() whitespace_between_tags = re.compile('>\s*<', ) expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() assert expect_html == result_html
def test_basic_xml(): """Test the simplest case with xml""" if not etree: # can't test it return html = """<html> <head> <title>Title</title> <style type="text/css"> img { border: none; } </style> </head> <body> <img src="test.png" alt="test"> </body> </html>""" expect_html = """<html> <head> <title>Title</title> </head> <body> <img src="test.png" alt="test" style="border:none"/> </body> </html>""" p = Premailer(html, method="xml") result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_empty_style_tag(): """empty style tag""" if not etree: # can't test it return html = """<html> <head> <title></title> <style type="text/css"></style> </head> <body> </body> </html>""" expect_html = """<html> <head> <title></title> </head> <body> </body> </html>""" p = Premailer(html) result_html = p.transform() whitespace_between_tags = re.compile('>\s*<', ) expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_xml_cdata(): """Test that CDATA is set correctly on remaining styles""" if not etree: # can't test it return html = """<html> <head> <title>Title</title> <style type="text/css"> span:hover > a { background: red; } </style> </head> <body> <span><a>Test</a></span> </body> </html>""" expect_html = """<html> <head> <title>Title</title> <style type="text/css">/*<![CDATA[*/span:hover > a {background:red}/*]]>*/</style> </head> <body> <span><a>Test</a></span> </body> </html>""" p = Premailer(html, method="xml") result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_doctype(): html = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> </head> <body> </body> </html>""" expect_html = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> </head> <body> </body> </html>""" p = Premailer(html) result_html = p.transform() whitespace_between_tags = re.compile('>\s*<', ) expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_keep_style_tags(): if not etree: # can't test it return html = """<html> <head> <style type="text/css"> h1 { color: red; } </style> </head> <body> <h1>Hello</h1> </body> </html>""" expect_html = """<html> <head> <style type="text/css"> h1 { color: red; } </style> </head> <body> <h1 style="color:red">Hello</h1> </body> </html>""" p = Premailer(html, keep_style_tags=True) result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_favour_rule_with_class_over_generic(): html = """<html> <head> <style> div.example { color: green; } div { color: black; } </style> </head> <body> <div class="example"></div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="color:green"></div> </body> </html>""" p = Premailer(html) result_html = p.transform() whitespace_between_tags = re.compile('>\s*<', ) expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def test_basic_html_with_pseudo_selector(): """test the simplest case""" if not etree: # can't test it return html = """ <html> <style type="text/css"> h1 { border:1px solid black } p { color:red;} p::first-letter { float:left; } </style> <h1 style="font-weight:bolder">Peter</h1> <p>Hej</p> </html> """ expect_html = """<html> <head> <style type="text/css">p::first-letter {float:left}</style> </head> <body> <h1 style="border:1px solid black; font-weight:bolder">Peter</h1> <p style="color:red">Hej</p> </body> </html>""" p = Premailer(html) result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)
def render_to_email_html_part(self): from ..lib.frontend_urls import FrontendUrls, URL_DISCRIMINANTS, SOURCE_DISCRIMINANTS from premailer import Premailer discussion = self.first_matching_subscription.discussion (assembl_css, ink_css) = self.get_css_paths(discussion) jinja_env = self.get_jinja_env() template_data = { 'subscription': self.first_matching_subscription, 'discussion': discussion, 'notification': self, 'frontendUrls': FrontendUrls(discussion), 'ink_css': ink_css.read(), 'assembl_notification_css': assembl_css.read(), 'discriminants': { 'url': URL_DISCRIMINANTS, 'source': SOURCE_DISCRIMINANTS }, 'jinja_env': jinja_env } if isinstance(self.post, SynthesisPost): template = jinja_env.get_template( 'notifications/html_mail_post_synthesis.jinja2') template_data['synthesis'] = self.post.publishes_synthesis else: template = jinja_env.get_template( 'notifications/html_mail_post.jinja2') html = template.render(**template_data) return Premailer(html, disable_leftover_css=True).transform()
def test_inline_wins_over_external(): html = """<html> <head> <style type="text/css"> div { text-align: left; } /* This tests that a second loop for the same style still doesn't * overwrite it. */ div { text-align: left; } </style> </head> <body> <div style="text-align:right">Some text</div> </body> </html>""" expect_html = """<html> <head> </head> <body> <div style="text-align:right" align="right">Some text</div> </body> </html>""" p = Premailer(html) result_html = p.transform() expect_html = whitespace_between_tags.sub('><', expect_html).strip() result_html = whitespace_between_tags.sub('><', result_html).strip() eq_(expect_html, result_html)