Package lamson :: Module html
[hide private]
[frames] | no frames]

Source Code for Module lamson.html

  1  """ 
  2  This implements an HTML Mail generator that uses templates and CleverCSS 
  3  to produce an HTML message with inline CSS attributes so that it will 
  4  display correctly.  As long as you can keep most of the HTML and CSS simple you 
  5  should have a high success rate at rendering this. 
  6   
  7  How it works is you create an HtmlMail class and configure it with a CleverCSS 
  8  stylesheet (also a template).  This acts as your template for the appearance and 
  9  the outer shell of your HTML. 
 10   
 11  When you go to send, you use a markdown content template to generate the 
 12  guts of your HTML.  You hand this, variables, and email headers to  
 13  HtmlMail.respond and it spits back a fully formed lamson.mail.MailResponse 
 14  ready to send. 
 15   
 16  The engine basically parses the CSS, renders your content template,  
 17  render your outer template, and then applies the CSS directly to your HTML 
 18  so your CSS attributes are inline and display in the HTML display. 
 19   
 20  Each element is a template loaded by your loader: the CleverCSS template, out HTML 
 21  template, and your own content. 
 22   
 23  Finally, use this as a generator by making one and having crank out all the emails 
 24  you need.  Don't make one HtmlMail for each message. 
 25  """ 
 26   
 27  from BeautifulSoup import BeautifulSoup 
 28  import clevercss 
 29  from lamson import mail, view 
 30  from markdown2 import markdown 
 31   
 32   
33 -class HtmlMail(object):
34 """ 35 Acts as a lamson.mail.MailResponse generator that produces a properly 36 formatted HTML mail message, including inline CSS applied to all HTML tags. 37 """
38 - def __init__(self, css_template, html_template, variables={}, wiki=markdown):
39 """ 40 You pass in a CleverCSS template (it'll be run through the template engine 41 before CleverCSS), the html_template, and any variables that the CSS template 42 needs. 43 44 The CSS template is processed once, the html_template is processed each time 45 you call render or respond. 46 47 If you don't like markdown, then you can set the wiki variable to any callable 48 that processes your templates. 49 """ 50 self.template = html_template 51 self.load_css(css_template, variables) 52 self.wiki = wiki
53
54 - def load_css(self, css_template, variables):
55 """ 56 If you want to change the CSS, simply call this with the new CSS and variables. 57 It will change internal state so that later calls to render or respond use 58 the new CSS. 59 """ 60 self.css = view.render(variables, css_template) 61 self.engine = clevercss.Engine(self.css) 62 self.stylesheet = [] 63 64 for selector, style in self.engine.evaluate(): 65 attr = "; ".join("%s: %s" % (k,v) for k,v in style) 66 selectors = selector[0].split() 67 # root, path, attr 68 self.stylesheet.append((selectors[0], selectors[1:], attr))
69 70
71 - def reduce_tags(self, name, tags):
72 """ 73 Used mostly internally to find all the tags that fit the given 74 CSS selector. It's fairly primitive, working only on tag names, 75 classes, and ids. You shouldn't get too fancy with the CSS you create. 76 """ 77 results = [] 78 79 for tag in tags: 80 if name.startswith("#"): 81 children = tag.findAll(attrs={"class": name[1:]}) 82 elif name.startswith("."): 83 children = tag.findAll(attrs={"id": name[1:]}) 84 else: 85 children = tag.findAll(name) 86 87 if children: 88 results += children 89 90 return results
91
92 - def apply_styles(self, html):
93 """ 94 Used mostly internally but helpful for testing, this takes the given HTML 95 and applies the configured CSS you've set. It returns a BeautifulSoup 96 object with all the style attributes set and nothing else changed. 97 """ 98 doc = BeautifulSoup(html) 99 roots = {} # the roots rarely change, even though the paths do 100 101 for root, path, attr in self.stylesheet: 102 tags = roots.get(root, None) 103 104 if not tags: 105 tags = self.reduce_tags(root, [doc]) 106 roots[root] = tags 107 108 for sel in path: 109 tags = self.reduce_tags(sel, tags) 110 111 112 for node in tags: 113 try: 114 node['style'] += "; " + attr 115 except KeyError: 116 node['style'] = attr 117 118 return doc
119 120
121 - def render(self, variables, content_template, pretty=False):
122 """ 123 Works like lamson.view.render, but uses apply_styles to modify 124 the HTML with the configured CSS before returning it to you. 125 126 If you set the pretty=True then it will prettyprint the results, 127 which is a waste of bandwidth, but helps when debugging. 128 129 Remember that content_template is run through the template system, 130 and then processed with self.wiki (defaults to markdown). This 131 let's you do template processing and write the HTML contents like 132 you would an email. 133 134 You could also attach the content_template as a text version of the 135 message for people without HTML. Simply set the .Body attribute 136 of the returned lamson.mail.MailResponse object. 137 """ 138 content = self.wiki(view.render(variables, content_template)) 139 lvars = variables.copy() 140 lvars['content'] = content 141 142 html = view.render(lvars, self.template) 143 styled = self.apply_styles(html) 144 145 if pretty: 146 return styled.prettify() 147 else: 148 return str(styled)
149 150
151 - def respond(self, variables, content, **kwd):
152 """ 153 Works like lamson.view.respond letting you craft a 154 lamson.mail.MailResponse immediately from the results of 155 a lamson.html.HtmlMail.render call. Simply pass in the 156 From, To, and Subject parameters you would normally pass 157 in for MailResponse, and it'll craft the HTML mail for 158 you and return it ready to deliver. 159 160 A slight convenience in this function is that if the 161 Body kw parameter equals the content parameter, then 162 it's assumed you want the raw markdown content to be 163 sent as the text version, and it will produce a nice 164 dual HTML/text email. 165 """ 166 assert content, "You must give a contents template." 167 168 if kwd.get('Body', None) == content: 169 kwd['Body'] = view.render(variables, content) 170 171 for key in kwd: 172 kwd[key] = kwd[key] % variables 173 174 msg = mail.MailResponse(**kwd) 175 msg.Html = self.render(variables, content) 176 177 return msg
178