--- 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)
--- 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)
--- 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
--- 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 <hX> 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 <ul>/<ol> containing <li>s
+ """
+
+ indent = self.styles.list_indent
+
+ # following lines, indented
+ yield rlpp.Indenter(indent)
+
+ # children
+ for sub in elem :
+ # get (optional) bullet for item
+ bullet = next(seq, None) if seq else None
+
+ # sub-render
+ for ff in self.render(sub, bullet) :
+ yield ff
+
+ # de-dent
+ yield rlpp.Indenter(-indent)
+
+ def render_ul (self, elem, bullet=None) : return self._render_list(elem, self.list_ul_seq(), bullet)
+ def render_ol (self, elem, bullet=None) : return self._render_list(elem, self.list_ol_seq(), bullet)
+
+ def render_li (self, elem, bullet) :
+ """
+ Render <li>, with given bullet on first line of text
+ """
+
+ children = list(elem)
+
+ # render bullet on first item
+ if elem.text :
+ # style for text pseudo-element
+ style = self.lookup_style(elem.makeelement('text', {}))
+
+ # direct text, no sub-p or such
+ yield rlpp.Paragraph(elem.text, style, bullet)
+
+ elif children :
+ sub = children.pop(0)
+
+ # render first sub with bullet
+ for ff in self.render(sub, bullet) :
+ yield ff
+
+ # render remaining elements
+ for sub in children :
+ for ff in self.render(sub) :
+ yield ff
+
+ ## Root element
+ def render_root (self, elem, bullet) :
+ """
+ Render the given root element's children, ignoring the root element itself.
+ """
+
+ for sub in elem :
+ for ff in self.render(sub) :
+ yield ff
+
+ ## Top-level element render dispatch
+ def render (self, elem, bullet=None) :
+ """
+ Yield a series of Flowables for the given element and its children.
+
+ elem - Element to (recursively) render
+ bullet - optional bullet text to use for this element
+ """
+
+ log.debug("%r, bullet=%r", elem, bullet)
+
+ try :
+ # lookup
+ func = getattr(self, 'render_%s' % elem.tag)
+
+ except AttributeError, ex :
+ raise Exception("Unhandled tag %r" % elem)
+
+ # enter element
+ self.stack.append(elem)
+
+ # dispatch
+ for ff in func(elem, bullet) :
+ yield ff
+
+ # exit element
+ assert self.stack.pop(-1) is elem
+