# HG changeset patch # User Tero Marttila # Date 1294356204 -7200 # Node ID e1b63e4d10f464cda739d058e738da82747a481e # Parent 97d5d37333d2dc91b7c783fd5c08fd1ea7a463e7 Implement Markup rendering for PDFs, and use that for order terms diff -r 97d5d37333d2 -r e1b63e4d10f4 README --- a/README Fri Jan 07 01:22:52 2011 +0200 +++ b/README Fri Jan 07 01:23:24 2011 +0200 @@ -7,4 +7,5 @@ * python-werkzeug (tested 0.6) * python-sqlalchemy (tested 0.5) * python-reportlab (tested 2.4) + * python-markdown (tested 2.0.3 - may have compatibility issues) diff -r 97d5d37333d2 -r e1b63e4d10f4 svv/controllers.py --- a/svv/controllers.py Fri Jan 07 01:22:52 2011 +0200 +++ b/svv/controllers.py Fri Jan 07 01:23:24 2011 +0200 @@ -194,6 +194,9 @@ """ Generate the document, and return it as a .pdf file, with the filename generated from the document's title. """ + + # XXX: proper support + self.process(**url_values) pdf_file = self.generate(**url_values) diff -r 97d5d37333d2 -r e1b63e4d10f4 svv/orders.py --- a/svv/orders.py Fri Jan 07 01:22:52 2011 +0200 +++ b/svv/orders.py Fri Jan 07 01:23:24 2011 +0200 @@ -6,7 +6,7 @@ from svv.controllers import PageHandler, DocumentHandler from svv.html import tags from svv import database as db -from svv import pdf +from svv import pdf, markup import datetime import logging @@ -1220,24 +1220,27 @@ return form - def load_order (self, id): + def process (self, id): """ Return OrderModel object for given ID """ - # XXX: really need that OrderModel object :) - form = OrderForm(self.app) - form.load(id) + # db session + self.session = self.app.session() + + # order object + self.order = self.session.query(Order).options(db.eagerload(Order.customer), db.eagerload(Order.contact)).get(id) - return form + # form params + self.params = self.load_params(self.POST) def generate_document (self, id) : + """ + Return PDF document to generate + """ - # retrieve from db - order = self.load_order(id) - - # params set by form - params = self.load_params(self.POST) + order = self.order + params = self.params title = "Teekkarispeksi Ry - Vuokrasopimus" author = "Teekkarispeksi Ry" @@ -1247,7 +1250,7 @@ ("", ""), ("", ""), ("", ""), - ("Vuokrasopimus", [order.customer_name, order.event_name, 'dd.mm.yy hh:mm-hh:mm']), + ("Vuokrasopimus", [order.customer.name, order.event_name, 'dd.mm.yy hh:mm-hh:mm']), ), footer_columns = ( ("Teekkarispeksi Ry", ("www.teekkarispeksi.fi", )), @@ -1262,30 +1265,60 @@ ) # stylesheet - styles = pdf.Styles + styles = pdf.Styles() from reportlab.platypus import Paragraph as p + - # contract terms - list_seq = pdf.ListItem.seq - terms = pdf.ListItem("Sopimusehdot", styles.h2, None, list_seq(), [ - pdf.ListItem("Osapuolet", styles.list_h2, None, list_seq(), [ - pdf.ListItem(None, None, "Teekkarispeksi ry (Y-tunnus 1888541-7), jäljempänä “Vuokranantaja”."), - pdf.ListItem(None, None, order.customer_name + u", jäljempänä “Vuokraaja”. 1.1 ja 1.2 jäljempänä yhdessä “osapuolet”.") - ]), - pdf.ListItem("Kaluston lainaaminen", styles.list_h2, None, list_seq(), [ - pdf.ListItem("Yleistä", styles.list_h3, "Tässä sopimuksessa sovitaan toiminnasta Vuokranantajan lainatessa tanssimattoja Vuokraajalle"), - pdf.ListItem("Vuokranantajan velvollisuudet", styles.list_h3, "Vuokranantaja sitoutuu toimittamaan sovittuna ajankohtana Vuokraajalle erikseen sovittava (liite) määrä tanssimattoja."), - pdf.ListItem("Blaa Blaa", styles.list_h3, "Etc."), - ]), - pdf.ListItem("Tätä sopimusta ja sitä koskevia erimielisyyksiä koskevat määräykset", styles.list_h2, None, list_seq(), [ - pdf.ListItem("Sopimuksen voimassaolo", styles.list_h3, "Sopimus on osapuolia sitova sen jälkeen, kun osapuolet ovat sen allekirjoittaneet."), - pdf.ListItem("Muutosten tekeminen", styles.list_h3, "Muutokset sopimukseen on tehtävä kirjallisesti molempien osapuolten kesken."), - pdf.ListItem("Blaa Blaa", styles.list_h3, "Etc."), - ]), - ]) + text = u"""\ +# Vuokrasopimus +### Teekkarispeksi ry AV-tekniikka +## Sopimusehdot +1. ## Osapuolet + 1. Teekkarispeksi ry (Y-tunnus 1888541-7), jäljempänä “Vuokranantaja”. + 2. {order.customer.name}, jäljempänä “Vuokraaja”. 1.1 ja 1.2 jäljempänä yhdessä “osapuolet”. + +2. ## Kaluston lainaaminen + 1. ### Yleistä + Tässä sopimuksessa sovitaan toiminnasta Vuokranantajan lainatessa tanssimattoja Vuokraajalle. + + 2. ### Vuokranantajan velvollisuudet + Vuokranantaja sitoutuu toimittamaan sovittuna ajankohtana Vuokraajalle erikseen sovittava (liite) määrä tanssimattoja. + + 3. ### Blaa Blaa + Etc. + +3. ## Tätä sopimusta ja sitä koskevia erimielisyyksiä koskevat määräykset + 1. ### Sopimuksen voimassaolo + Sopimus on osapuolia sitova sen jälkeen, kun osapuolet ovat sen allekirjoittaneet. + + 2. ### Muutosten tekeminen + Muutokset sopimukseen on tehtävä kirjallisesti molempien osapuolten kesken. + + 3. ### Blaa Blaa + + Etc. + +## Nouto + Aika: _______________ Paikka: _______________ + +## Palautus + Aika: _______________ Paikka: _______________ + + """ + + # format + text = text.format( + order = order, + ) + + # parse to doc tree + root = markup.Markup().parse(text) + + # format to flowables + text_elements = list(pdf.Markup(styles).render(root)) sig_prefill = {} @@ -1295,16 +1328,9 @@ if params.prefill_ourname : sig_prefill[('Vuokranantaja', 'Nimen selvennys')] = params.prefill_ourname - elements = [ - p("Vuokrasopimus", styles.h1), - p("Teekkarispeksi ry AV-tekniikka", styles.h3), - ] + list(terms.render_pdf()) + [ - p("Nouto", styles.h2), - p("\t\tAika: _______________\tPaikka: _______________", styles.text), - p("Palautus", styles.h2), - p("\t\tAika: _______________\tPaikka: _______________", styles.text), - + elements = text_elements + [ pdf.SignatureBlock(("Vuokranantaja", "Vuokraaja"), ("%(column)s", "Nimen selvennys", "Aika ja paikka"), sig_prefill), + ] # ok diff -r 97d5d37333d2 -r e1b63e4d10f4 svv/pdf.py --- a/svv/pdf.py Fri Jan 07 01:22:52 2011 +0200 +++ b/svv/pdf.py Fri Jan 07 01:23:24 2011 +0200 @@ -23,7 +23,15 @@ samplestyles = styles.getSampleStyleSheet() + # default + default = styles.ParagraphStyle('Text', samplestyles['Normal'], + + ) + # normal text + text = p = default + + # headers h1 = styles.ParagraphStyle('Heading1', samplestyles['h1'], fontName = 'Times-Bold', fontSize = 22, @@ -46,9 +54,6 @@ spaceAfter = 0, ) - text = styles.ParagraphStyle('Text', samplestyles['Normal'], - - ) # list indent level list_indent = inch / 4 @@ -82,86 +87,55 @@ ) # infot - list_text = styles.ParagraphStyle('ListText', samplestyles['Normal'], + list_p = styles.ParagraphStyle('ListText', samplestyles['Normal'], bulletIndent = 0, leftIndent = list_indent, ) - -class ListItem (object) : - """ - Indented/nested list - """ + list_text = list_p + # lookup styles by rule + # searched in order, first match against end of node_path wins + RULES = ( + ('li p', list_p), + ('li h1', list_h1), + ('li h2', list_h2), + ('li h3', list_h3), + + ('p', p), + ('h1', h1), + ('h2', h2), + ('h3', h3), + + ) + @classmethod - def seq (cls) : + def match (cls, name=None) : """ - List numbering. - - Fixed as numeric only for now + Return appropriate style. """ - for idx in itertools.count(1) : - yield "%d." % (idx, ) + if name : + return getattr(cls, name, cls.default) + else : + return cls.default - def __init__ (self, title, title_style, text, subseq=None, sublist=None, - text_style=Styles.list_text, indent=Styles.list_indent) : + def lookup (self, node_path) : """ - title - title to display as first line - title_style - paragraph style for title line - text - multi-line texto to display on first or previous lines - subseq - sequence of bullet-texts to use for items in sublist - sublist - sequence of sub-nodes - - text_style - paragraph style for text lines - indent - indentation for text and sub-lists + Return a suitable ParagraphStyle to use for a node + + node_path - tag-path to node (list of tags as ancestry of node) """ - self.title = title - self.title_style = title_style - self.text = text - - self.subseq = subseq - self.sublist = sublist - - self.text_style = text_style - self.indent = indent - - def render_pdf (self, bullet=None) : - """ - Yield a series of PDF flowables for this list node and sub-nodes, useable for pdf.DocTemplateBase.build() - - bullet - bullet text to use for this item's paragraph - """ - - # first line, with possible bullet - if self.title : - yield rlpp.Paragraph(self.title, self.title_style, bullet) + # XXX: find a better hack + nodepath = ' '.join(node_path) - elif self.text : - yield rlpp.Paragraph(self.text, self.text_style, bullet) - - # indented text after title - if self.title and self.text : - yield rlpp.Paragraph(self.text, self.text_style) - - if self.sublist : - # following lines, indented - yield rlpp.Indenter(self.indent) + for rule, style in self.RULES : + if nodepath.endswith(rule) : + return style - # sub-items - for item in self.sublist : - # get (optional) bullet for item - bullet = next(self.subseq, None) if self.subseq else None - - # render item as series of elements - for element in item.render_pdf(bullet) : - yield element - - # de-dent - yield rlpp.Indenter(-self.indent) - - + # default + return self.default class SignatureBlock (rlpp.Flowable) : """ @@ -453,3 +427,171 @@ # binary data out return buf.getvalue() +class Markup (object) : + """ + Generate Paragraphs from Markup docs. + """ + + def __init__ (self, styles) : + """ + Initialize render state. + + styles - Style instance to use + + This class is stateful and single-use. + + """ + + # elment stack + self.stack = [] + + # styles + self.styles = styles + + def lookup_style (self, elem=None) : + """ + Lookup style for given element, which must be at the top of the stack. + """ + + # path to element + path = self.stack + + if elem is not None and path[-1] is not elem : + # XXX: sub-elem? + path = path + [elem] + + # lookup + return self.styles.lookup([elem.tag for elem in path]) + + ## Basic text + def render_p (self, elem, bullet=None) : + """ + Normal paragraph. + + XXX: inline markup? + """ + + style = self.lookup_style(elem) + + yield rlpp.Paragraph(elem.text, style, bullet) + + def _render_hx (self, elem, bullet=None) : + """ + Render given h1/h2/h3 element + """ + + # get style for tag + style = self.lookup_style(elem) + + # styled text + yield rlpp.Paragraph(elem.text, style, bullet) + + render_h1 = render_h2 = render_h3 = _render_hx + + ## Lists + def list_ol_seq (self) : + """ + Numeric ordered list numbering. + """ + + for idx in itertools.count(1) : + yield "%d." % (idx, ) + + def list_ul_seq (self) : + """ + Bulleted unordered list numbering. + """ + + while True : + yield "•" + + def _render_list (self, elem, seq, bullet=None) : + """ + Render