merge 3.5
authorAurelien Campeas <aurelien.campeas@logilab.fr>
Thu, 20 Aug 2009 18:37:33 +0200
branch3.5
changeset 2938 e5cef8ff5857
parent 2931 17224e90a1c4 (current diff)
parent 2937 36b11249014e (diff)
child 2939 a613cc003ab7
merge
debian/control
--- a/common/test/unittest_uilib.py	Thu Aug 20 17:57:56 2009 +0200
+++ b/common/test/unittest_uilib.py	Thu Aug 20 18:37:33 2009 +0200
@@ -81,45 +81,6 @@
             got = uilib.text_cut(text, 30)
             self.assertEquals(got, expected)
 
-tree = ('root', (
-    ('child_1_1', (
-    ('child_2_1', ()), ('child_2_2', (
-    ('child_3_1', ()),
-    ('child_3_2', ()),
-    ('child_3_3', ()),
-    )))),
-    ('child_1_2', (('child_2_3', ()),))))
-
-generated_html = """\
-<table class="tree">
-<tr><td class="tree_cell" rowspan="2"><div class="tree_cell">root</div></td><td class="tree_cell_1_1">&nbsp;</td><td class="tree_cell_1_2">&nbsp;</td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_1_1</div></td><td class="tree_cell_1_1">&nbsp;</td><td class="tree_cell_1_2">&nbsp;</td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_2_1</div></td><td class="tree_cell_0_1">&nbsp;</td><td class="tree_cell_0_2">&nbsp;</td><td rowspan="2">&nbsp;</td></tr>
-<tr><td class="tree_cell_1_3">&nbsp;</td><td class="tree_cell_1_4">&nbsp;</td><td class="tree_cell_1_3">&nbsp;</td><td class="tree_cell_1_4">&nbsp;</td><td class="tree_cell_0_3">&nbsp;</td><td class="tree_cell_0_4">&nbsp;</td></tr>
-<tr><td rowspan="2">&nbsp;</td><td class="tree_cell_2_1">&nbsp;</td><td class="tree_cell_2_2">&nbsp;</td><td rowspan="2">&nbsp;</td><td class="tree_cell_4_1">&nbsp;</td><td class="tree_cell_4_2">&nbsp;</td><td class="tree_cell" rowspan="2"><div id="selected" class="tree_cell">child_2_2</div></td><td class="tree_cell_1_1">&nbsp;</td><td class="tree_cell_1_2">&nbsp;</td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_3_1</div></td></tr>
-<tr><td class="tree_cell_2_3">&nbsp;</td><td class="tree_cell_2_4">&nbsp;</td><td class="tree_cell_4_3">&nbsp;</td><td class="tree_cell_4_4">&nbsp;</td><td class="tree_cell_1_3">&nbsp;</td><td class="tree_cell_1_4">&nbsp;</td></tr>
-<tr><td rowspan="2">&nbsp;</td><td class="tree_cell_2_1">&nbsp;</td><td class="tree_cell_2_2">&nbsp;</td><td rowspan="2">&nbsp;</td><td class="tree_cell_0_1">&nbsp;</td><td class="tree_cell_0_2">&nbsp;</td><td rowspan="2">&nbsp;</td><td class="tree_cell_3_1">&nbsp;</td><td class="tree_cell_3_2">&nbsp;</td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_3_2</div></td></tr>
-<tr><td class="tree_cell_2_3">&nbsp;</td><td class="tree_cell_2_4">&nbsp;</td><td class="tree_cell_0_3">&nbsp;</td><td class="tree_cell_0_4">&nbsp;</td><td class="tree_cell_3_3">&nbsp;</td><td class="tree_cell_3_4">&nbsp;</td></tr>
-<tr><td rowspan="2">&nbsp;</td><td class="tree_cell_2_1">&nbsp;</td><td class="tree_cell_2_2">&nbsp;</td><td rowspan="2">&nbsp;</td><td class="tree_cell_0_1">&nbsp;</td><td class="tree_cell_0_2">&nbsp;</td><td rowspan="2">&nbsp;</td><td class="tree_cell_4_1">&nbsp;</td><td class="tree_cell_4_2">&nbsp;</td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_3_3</div></td></tr>
-<tr><td class="tree_cell_2_3">&nbsp;</td><td class="tree_cell_2_4">&nbsp;</td><td class="tree_cell_0_3">&nbsp;</td><td class="tree_cell_0_4">&nbsp;</td><td class="tree_cell_4_3">&nbsp;</td><td class="tree_cell_4_4">&nbsp;</td></tr>
-<tr><td rowspan="2">&nbsp;</td><td class="tree_cell_4_1">&nbsp;</td><td class="tree_cell_4_2">&nbsp;</td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_1_2</div></td><td class="tree_cell_5_1">&nbsp;</td><td class="tree_cell_5_2">&nbsp;</td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_2_3</div></td><td class="tree_cell_0_1">&nbsp;</td><td class="tree_cell_0_2">&nbsp;</td><td rowspan="2">&nbsp;</td></tr>
-<tr><td class="tree_cell_4_3">&nbsp;</td><td class="tree_cell_4_4">&nbsp;</td><td class="tree_cell_5_3">&nbsp;</td><td class="tree_cell_5_4">&nbsp;</td><td class="tree_cell_0_3">&nbsp;</td><td class="tree_cell_0_4">&nbsp;</td></tr>
-</table>\
-"""
-
-def make_tree(tuple):
-    n = Node(tuple[0])
-    for child in tuple[1]:
-        n.append(make_tree(child))
-    return n
-
-class UIlibHTMLGenerationTC(TestCase):
-    """ a basic tree node, caracterised by an id"""
-    def setUp(self):
-        """ called before each test from this class """
-        self.o = make_tree(tree)
-
-    def test_generated_html(self):
-        s = uilib.render_HTML_tree(self.o, selected_node="child_2_2")
-        self.assertTextEqual(s, generated_html)
 
 
 if __name__ == '__main__':
--- a/common/uilib.py	Thu Aug 20 17:57:56 2009 +0200
+++ b/common/uilib.py	Thu Aug 20 18:37:33 2009 +0200
@@ -12,7 +12,6 @@
 
 import csv
 import re
-from urllib import quote as urlquote
 from StringIO import StringIO
 
 from logilab.mtconverter import xml_escape, html_unescape
@@ -264,124 +263,6 @@
         res = unicode(res, 'UTF8')
     return res
 
-def render_HTML_tree(tree, selected_node=None, render_node=None, caption=None):
-    """
-    Generate a pure HTML representation of a tree given as an instance
-    of a logilab.common.tree.Node
-
-    selected_node is the currently selected node (if any) which will
-    have its surrounding <div> have id="selected" (which default
-    to a bold border libe with the default CSS).
-
-    render_node is a function that should take a Node content (Node.id)
-    as parameter and should return a string (what will be displayed
-    in the cell).
-
-    Warning: proper rendering of the generated html code depends on html_tree.css
-    """
-    tree_depth = tree.depth_down()
-    if render_node is None:
-        render_node = str
-
-    # helper function that build a matrix from the tree, like:
-    # +------+-----------+-----------+
-    # | root | child_1_1 | child_2_1 |
-    # | root | child_1_1 | child_2_2 |
-    # | root | child_1_2 |           |
-    # | root | child_1_3 | child_2_3 |
-    # | root | child_1_3 | child_2_4 |
-    # +------+-----------+-----------+
-    # from:
-    # root -+- child_1_1 -+- child_2_1
-    #       |             |
-    #       |             +- child_2_2
-    #       +- child_1_2
-    #       |
-    #       +- child1_3 -+- child_2_3
-    #                    |
-    #                    +- child_2_2
-    def build_matrix(path, matrix):
-        if path[-1].is_leaf():
-            matrix.append(path[:])
-        else:
-            for child in path[-1].children:
-                build_matrix(path[:] + [child], matrix)
-
-    matrix = []
-    build_matrix([tree], matrix)
-
-    # make all lines in the matrix have the same number of columns
-    for line in matrix:
-        line.extend([None]*(tree_depth-len(line)))
-    for i in range(len(matrix)-1, 0, -1):
-        prev_line, line = matrix[i-1:i+1]
-        for j in range(len(line)):
-            if line[j] == prev_line[j]:
-                line[j] = None
-
-    # We build the matrix of link types (between 2 cells on a line of the matrix)
-    # link types are :
-    link_types = {(True,  True,  True ): 1, # T
-                  (False, False, True ): 2, # |
-                  (False, True,  True ): 3, # + (actually, vert. bar with horiz. bar on the right)
-                  (False, True,  False): 4, # L
-                  (True,  True,  False): 5, # -
-                  }
-    links = []
-    for i, line in enumerate(matrix):
-        links.append([])
-        for j in range(tree_depth-1):
-            cell_11 = line[j] is not None
-            cell_12 = line[j+1] is not None
-            cell_21 = line[j+1] is not None and line[j+1].next_sibling() is not None
-            link_type = link_types.get((cell_11, cell_12, cell_21), 0)
-            if link_type == 0 and i > 0 and links[i-1][j] in (1, 2, 3):
-                link_type = 2
-            links[-1].append(link_type)
-
-
-    # We can now generate the HTML code for the <table>
-    s = u'<table class="tree">\n'
-    if caption:
-        s += '<caption>%s</caption>\n' % caption
-
-    for i, link_line in enumerate(links):
-        line = matrix[i]
-
-        s += '<tr>'
-        for j, link_cell in enumerate(link_line):
-            cell = line[j]
-            if cell:
-                if cell.id == selected_node:
-                    s += '<td class="tree_cell" rowspan="2"><div id="selected" class="tree_cell">%s</div></td>' % (render_node(cell.id))
-                else:
-                    s += '<td class="tree_cell" rowspan="2"><div class="tree_cell">%s</div></td>' % (render_node(cell.id))
-            else:
-                s += '<td rowspan="2">&nbsp;</td>'
-            s += '<td class="tree_cell_%d_1">&nbsp;</td>' % link_cell
-            s += '<td class="tree_cell_%d_2">&nbsp;</td>' % link_cell
-
-        cell = line[-1]
-        if cell:
-            if cell.id == selected_node:
-                s += '<td class="tree_cell" rowspan="2"><div id="selected" class="tree_cell">%s</div></td>' % (render_node(cell.id))
-            else:
-                s += '<td class="tree_cell" rowspan="2"><div class="tree_cell">%s</div></td>' % (render_node(cell.id))
-        else:
-            s += '<td rowspan="2">&nbsp;</td>'
-
-        s += '</tr>\n'
-        if link_line:
-            s += '<tr>'
-            for j, link_cell in enumerate(link_line):
-                s += '<td class="tree_cell_%d_3">&nbsp;</td>' % link_cell
-                s += '<td class="tree_cell_%d_4">&nbsp;</td>' % link_cell
-            s += '</tr>\n'
-
-    s += '</table>'
-    return s
-
-
 
 # traceback formatting ########################################################
 
--- a/debian/control	Thu Aug 20 17:57:56 2009 +0200
+++ b/debian/control	Thu Aug 20 18:37:33 2009 +0200
@@ -62,7 +62,7 @@
 Architecture: all
 XB-Python-Version: ${python:Versions}
 Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), python-simplejson (>= 1.3), python-elementtree
-Recommends: python-docutils, python-vobject, fckeditor, python-fyzz
+Recommends: python-docutils, python-vobject, fckeditor, python-fyzz, python-pysixt, fop
 Description: web interface library for the CubicWeb framework
  CubicWeb is a semantic web application framework.
  .
--- a/utils.py	Thu Aug 20 17:57:56 2009 +0200
+++ b/utils.py	Thu Aug 20 18:37:33 2009 +0200
@@ -320,9 +320,27 @@
                                                  self.body.getvalue())
 
 
-class AcceptMixIn(object):
-    """Mixin class for appobjects defining the 'accepts' attribute describing
-    a set of supported entity type (Any by default).
+def can_do_pdf_conversion(__answer=[None]):
+    """pdf conversion depends on
+    * pyxmltrf (python package)
+    * fop 0.9x
     """
-    # XXX deprecated, no more necessary
-
+    if __answer[0] is not None:
+        return __answer[0]
+    try:
+        import pysixt
+    except ImportError:
+        __answer[0] = False
+        return False
+    from subprocess import Popen, STDOUT
+    import os
+    try:
+        Popen(['/usr/bin/fop', '-q'],
+              stdout=open(os.devnull, 'w'),
+              stderr=STDOUT)
+    except OSError, e:
+        print e
+        __answer[0] = False
+        return False
+    __answer[0] = True
+    return True
--- a/web/__init__.py	Thu Aug 20 17:57:56 2009 +0200
+++ b/web/__init__.py	Thu Aug 20 18:37:33 2009 +0200
@@ -16,7 +16,7 @@
 
 from logilab.common.deprecation import deprecated
 
-from cubicweb.common.uilib import urlquote
+from urllib import quote as urlquote
 from cubicweb.web._exceptions import *
 
 
--- a/web/data/cubicweb.css	Thu Aug 20 17:57:56 2009 +0200
+++ b/web/data/cubicweb.css	Thu Aug 20 18:37:33 2009 +0200
@@ -838,4 +838,13 @@
   border: 1px solid #edecd2;
   border-color:#edecd2 #cfceb7 #cfceb7  #edecd2;
   background: #fffff8 url("button.png") bottom left repeat-x;
-}
\ No newline at end of file
+}
+
+
+/********************************/
+/* placement of alt. view icons */
+/********************************/
+
+.otherView {
+  float: right;
+}
Binary file web/data/pdf_icon.gif has changed
--- a/web/views/basecomponents.py	Thu Aug 20 17:57:56 2009 +0200
+++ b/web/views/basecomponents.py	Thu Aug 20 18:37:33 2009 +0200
@@ -2,6 +2,7 @@
 
 * the rql input form
 * the logged user link
+* pdf view link
 
 :organization: Logilab
 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
@@ -214,6 +215,23 @@
         self.w(u'&nbsp;|&nbsp;'.join(html))
         self.w(u'</div>')
 
+class PdfViewComponent(component.Component):
+    id = 'pdfview'
+    __select__ = yes()
+
+    context = 'header'
+    property_defs = {
+        _('visible'):  dict(type='Boolean', default=True,
+                            help=_('display the pdf icon or not')),
+    }
+
+    def call(self, vid):
+        self.req.add_css('cubes.confman.css')
+        entity = self.entity(0,0)
+        self.w(u'<a href="%s" class="otherView"><img src="data/pdf_icon.gif"/></a>' %
+               (xml_escape(entity.absolute_url() + '?vid=%s&__template=pdf-main-template' % vid)))
+
+
 
 def registration_callback(vreg):
     vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,))
--- a/web/views/basetemplates.py	Thu Aug 20 17:57:56 2009 +0200
+++ b/web/views/basetemplates.py	Thu Aug 20 18:37:33 2009 +0200
@@ -13,7 +13,7 @@
 from cubicweb.appobject import objectify_selector
 from cubicweb.selectors import match_kwargs
 from cubicweb.view import View, MainTemplate, NOINDEX, NOFOLLOW
-from cubicweb.utils import make_uid, UStringIO
+from cubicweb.utils import make_uid, UStringIO, can_do_pdf_conversion
 
 
 # main templates ##############################################################
@@ -266,6 +266,44 @@
             self.w(u'</td>\n')
             self.w(u'</tr></table>\n')
 
+if can_do_pdf_conversion():
+    from xml.etree.cElementTree import ElementTree
+    from subprocess import Popen as sub
+    from StringIO import StringIO
+    from tempfile import NamedTemporaryFile
+    from cubicweb.web.xhtml2fo import ReportTransformer
+
+    class PdfMainTemplate(TheMainTemplate):
+        id = 'pdf-main-template'
+
+        def call(self, view):
+            """build the standard view, then when it's all done, convert xhtml to pdf
+            """
+            super(PdfMainTemplate, self).call(view)
+            pdf = self.to_pdf(self._stream)
+            self.req.set_content_type('application/pdf', filename='report.pdf')
+            self.binary = True
+            self.w = None
+            self.set_stream()
+            # pylint needs help
+            self.w(pdf)
+
+        def to_pdf(self, stream, section='contentmain'):
+            # XXX see ticket/345282
+            stream = stream.getvalue().replace('&nbsp;', '&#160;').encode('utf-8')
+            xmltree = ElementTree()
+            xmltree.parse(StringIO(stream))
+            foptree = ReportTransformer(section).transform(xmltree)
+            foptmp = NamedTemporaryFile()
+            pdftmp = NamedTemporaryFile()
+            foptree.write(foptmp)
+            foptmp.flush()
+            fopproc = sub(['/usr/bin/fop', foptmp.name, pdftmp.name])
+            fopproc.wait()
+            pdftmp.seek(0)
+            pdf = pdftmp.read()
+            return pdf
+
 # page parts templates ########################################################
 
 class HTMLHeader(View):
@@ -489,4 +527,4 @@
 
 ## vregistry registration callback ############################################
 def registration_callback(vreg):
-    vreg.register_all(globals().values(), modname=__name__)
+    vreg.register_all(globals().values(), __name__)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/xhtml2fo.py	Thu Aug 20 18:37:33 2009 +0200
@@ -0,0 +1,142 @@
+from cubicweb.utils import can_do_pdf_conversion
+assert can_do_pdf_conversion()
+
+from xml.etree.ElementTree import QName, fromstring
+from pysixt.standard.xhtml_xslfo.transformer import XHTML2FOTransformer
+from pysixt.utils.xslfo.standard import cm
+from pysixt.utils.xslfo import SimplePageMaster
+from pysixt.standard.xhtml_xslfo.default_styling import default_styles
+from pysixt.standard.xhtml_xslfo import XHTML_NS
+
+
+class ReportTransformer(XHTML2FOTransformer):
+    """
+    Class transforming an XHTML input tree into a FO document
+    displaying reports (one report for each <div class="contentmain">
+    element in the input tree.
+    """
+
+    def __init__(self, section,
+                 page_width=21.0, page_height=29.7,
+                 margin_top=1.0, margin_bottom=1.0,
+                 margin_left=1.0, margin_right=1.0,
+                 header_footer_height=0.75,
+                 standard_font_size=11.0, default_lang=u"fr" ):
+        """
+        Initializes a transformer turning an XHTML input tree
+        containing <div class="contentmain"> elements representing
+        main content sections into a FO output tree displaying the
+        reports.
+
+        page_width: float - width of the page (in cm)
+        page_height: float - height of the page (in cm)
+        margin_top: float - top margin of the page (in cm)
+        margin_bottom: float - bottom margin of the page (in cm)
+        margin_left: float - left margin of the page (in cm)
+        margin_right: float - right margin of the page (in cm)
+        header_footer_height: float - height of the header or the footer of the
+                              page that the page number (if any) will be
+                              inserted in.
+        standard_font_size: float - standard size of the font (in pt)
+        default_lang: u"" - default language (used for hyphenation)
+        """
+        self.section = section
+        self.page_width = page_width
+        self.page_height = page_height
+
+        self.page_tmargin = margin_top
+        self.page_bmargin = margin_bottom
+        self.page_lmargin = margin_left
+        self.page_rmargin = margin_right
+
+        self.hf_height = header_footer_height
+
+        self.font_size = standard_font_size
+        self.lang = default_lang
+
+        XHTML2FOTransformer.__init__(self)
+
+
+    def define_pagemasters(self):
+        """
+        Defines the page masters for the FO output document.
+        """
+        pm = SimplePageMaster(u"page-report")
+        pm.set_page_dims( self.page_width*cm, self.page_height*cm )
+        pm.set_page_margins({u'top'   : self.page_tmargin*cm,
+                             u'bottom': self.page_bmargin*cm,
+                             u'left'  : self.page_lmargin*cm,
+                             u'right' : self.page_rmargin*cm })
+        pm.add_peripheral_region(u"end",self.hf_height)
+        dims = {}
+        dims[u"bottom"] = self.hf_height + 0.25
+        pm.set_main_region_margins(dims)
+        return [pm]
+
+    def _visit_report(self, in_elt, _out_elt, params):
+        """
+        Specific visit function for the input <div> elements whose class is
+        "report". The _root_visit method of this class selects these input
+        elements and asks the process of these elements with this specific
+        visit function.
+        """
+
+        ps = self.create_pagesequence(u"page-report")
+        props = { u"force-page-count": u"no-force",
+                  u"initial-page-number": u"1",
+                  u"format": u"1", }
+        self._output_properties(ps,props)
+
+        sc = self.create_staticcontent(ps, u"end")
+        sc_bl = self.create_block(sc)
+        attrs = { u"hyphenate": u"false", }
+        attrs[u"font-size"] = u"%.1fpt" %(self.font_size*0.7)
+        attrs[u"language"] = self.lang
+        attrs[u"text-align"] = u"center"
+        self._output_properties(sc_bl,attrs)
+        sc_bl.text = u"Page" + u" " # ### Should be localised!
+        pn = self.create_pagenumber(sc_bl)
+        pn.tail = u"/"
+        lpn = self.create_pagenumbercitation( sc_bl,
+                                              u"last-block-of-report-%d" % params[u"context_pos"]
+                                              )
+
+
+        fl = self.create_flow(ps,u"body")
+        bl = self.create_block(fl)
+
+        # Sets on the highest block element the properties of the XHTML body
+        # element. These properties (at the least the inheritable ones) will
+        # be inherited by all the future FO elements.
+        bodies = list(self.in_tree.getiterator(QName(XHTML_NS,u"body")))
+        if len(bodies) > 0:
+            attrs = self._extract_properties([bodies[0]])
+        else:
+            attrs = default_styles[u"body"].copy()
+        attrs[u"font-size"] = u"%.1fpt" %self.font_size
+        attrs[u"language"] = self.lang
+        self._output_properties(bl,attrs)
+
+        # Processes the report content
+        self._copy_text(in_elt,bl)
+        self._process_nodes(in_elt.getchildren(),bl)
+
+        # Inserts an empty block at the end of the report in order to be able
+        # to compute the last page number of this report.
+        last_bl = self.create_block(bl)
+        props = { u"keep-with-previous": u"always", }
+        props[u"id"] = u"last-block-of-report-%d" % params[u"context_pos"]
+        self._output_properties(last_bl,props)
+
+
+    def _root_visit(self):
+        """
+        Visit function called when starting the process of the input tree.
+        """
+        content = [ d for d in self.in_tree.getiterator(QName(XHTML_NS,u"div"))
+                    if d.get(u"id") == self.section ]
+        # Asks the process of the report elements with a specific visit
+        # function
+        self._process_nodes(content, self.fo_root,
+                            with_function=self._visit_report)
+