Commit d4797da9 authored by Jérome Perrin's avatar Jérome Perrin

Unit tests for PDFForm.

Some cleanup in PDFForm, and minor changes to make it easier to test.



git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@13206 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent a02d9bd4
...@@ -52,10 +52,10 @@ try: ...@@ -52,10 +52,10 @@ try:
except ImportError: except ImportError:
SUPPORTS_WEBDAV_LOCKS = 0 SUPPORTS_WEBDAV_LOCKS = 0
# FIXME: Programs linked against mandriva libgcj v 3.4.0 ave a strange # Programs linked against mandriva libgcj v 3.4.0 ave a strange issue that make
# issue that make them impossible to popen within zope. # them impossible to popen within zope. That's why we do not use the 'real'
# That's why we do not use the 'real' pdftk but a replacement program, # pdftk but a replacement program, pdftk-emulation available from nexedi's RPM
# pdftk-emulation available from nexedi's RPM repositories. # repositories.
PDFTK_EXECUTABLE = "pdftk-emulation" PDFTK_EXECUTABLE = "pdftk-emulation"
# With python >= 2.4 and zope >= 2.7.8, pdftk-emulation is no longer needed # With python >= 2.4 and zope >= 2.7.8, pdftk-emulation is no longer needed
...@@ -71,9 +71,8 @@ if python_version >= 204 and zope_version >= 20708: ...@@ -71,9 +71,8 @@ if python_version >= 204 and zope_version >= 20708:
PDFTK_EXECUTABLE = "pdftk" PDFTK_EXECUTABLE = "pdftk"
class PDFTk : class PDFTk:
""" """A class to wrapp calls to pdftk executable, found at
A class to wrapp calls to pdftk executable, found at
http://www.accesspdf.com/pdftk/ http://www.accesspdf.com/pdftk/
""" """
def catPages(self, pdfFile, cat_option) : def catPages(self, pdfFile, cat_option) :
...@@ -193,6 +192,7 @@ class PDFTk : ...@@ -193,6 +192,7 @@ class PDFTk :
fdf += "trailer\x0d<<\x0d/Root 1 0 R \x0d\x0d>>\x0d%%EOF\x0d\x0a" fdf += "trailer\x0d<<\x0d/Root 1 0 R \x0d\x0d>>\x0d%%EOF\x0d\x0a"
return fdf return fdf
# Constructors # Constructors
manage_addPDFForm = DTMLFile("dtml/PDFForm_add", globals()) manage_addPDFForm = DTMLFile("dtml/PDFForm_add", globals())
def addPDFForm(self, id, title="", pdf_file=None, REQUEST=None): def addPDFForm(self, id, title="", pdf_file=None, REQUEST=None):
...@@ -203,7 +203,7 @@ def addPDFForm(self, id, title="", pdf_file=None, REQUEST=None): ...@@ -203,7 +203,7 @@ def addPDFForm(self, id, title="", pdf_file=None, REQUEST=None):
# upload content # upload content
if pdf_file: if pdf_file:
self._getOb(id).manage_upload(pdf_file) self._getOb(id).manage_upload(pdf_file)
self._getOb(id).content_type="application/pdf" self._getOb(id).content_type = "application/pdf"
if REQUEST : if REQUEST :
u = REQUEST['URL1'] u = REQUEST['URL1']
...@@ -211,9 +211,9 @@ def addPDFForm(self, id, title="", pdf_file=None, REQUEST=None): ...@@ -211,9 +211,9 @@ def addPDFForm(self, id, title="", pdf_file=None, REQUEST=None):
u = "%s/%s" % (u, quote(id)) u = "%s/%s" % (u, quote(id))
REQUEST.RESPONSE.redirect(u+'/manage_main') REQUEST.RESPONSE.redirect(u+'/manage_main')
class CalculatedValues : class CalculatedValues :
""" """This class holds a reference to calculated values, for use in TALES,
This class holds a reference to calculated values, for use in TALES,
because in PDF Form filling, there is lots of references to others cell because in PDF Form filling, there is lots of references to others cell
values (sums ...). This class will be in TALES context under the key 'cell' values (sums ...). This class will be in TALES context under the key 'cell'
...@@ -237,21 +237,37 @@ class CalculatedValues : ...@@ -237,21 +237,37 @@ class CalculatedValues :
# doesn't complain that NoneType doesn't support + when a1 not found # doesn't complain that NoneType doesn't support + when a1 not found
return self.__values[attr] return self.__values[attr]
__getattr__ = __getitem__ __getattr__ = __getitem__
allow_class(CalculatedValues) allow_class(CalculatedValues)
class CircularReferencyError(ValueError):
"""A circular reference is found trying to evaluate cell TALES."""
class EmptyERP5PdfFormError(Exception): class EmptyERP5PdfFormError(Exception):
"""Error thrown when you try to display an empty Pdf. """ """Error thrown when you try to display an empty Pdf. """
allow_class(EmptyERP5PdfFormError) allow_class(EmptyERP5PdfFormError)
class PDFForm(File): class PDFForm(File):
""" """This class allows to fill PDF Form with TALES expressions,
This class allows to fill PDF Form with TALES expressions,
using a TALES expression for each cell. using a TALES expression for each cell.
TODO:
* cache compiled TALES
* set _v_errors when setting invalid TALES (setCellTALES can raise, but
not doEditCells)
""" """
meta_type = "ERP5 PDF Form" meta_type = "ERP5 PDF Form"
icon = "www/PDFForm.png" icon = "www/PDFForm.png"
# Those 2 are ugly names, but we keep compatibility
# the page range we want to print (a TALES expr)
__page_range__ = ''
# the method to format values (a TALES expr)
__format_method__ = ''
# Declarative Security # Declarative Security
security = ClassSecurityInfo() security = ClassSecurityInfo()
...@@ -277,21 +293,16 @@ class PDFForm(File): ...@@ -277,21 +293,16 @@ class PDFForm(File):
filter(lambda option:option['label'] != "View", File.manage_options) filter(lambda option:option['label'] != "View", File.manage_options)
) )
# XXX This non thread-safeness is probably a problem under high load
pdftk = PDFTk() pdftk = PDFTk()
def __init__ (self, id, title, pdf_file) : def __init__ (self, id, title='', pdf_file=''):
# holds all the cell informations, even those not related to this form # holds all the cell informations, even those not related to this form
self.all_cells = PersistentMapping() self.all_cells = PersistentMapping()
# holds the cells related to this pdf form # holds the cells related to this pdf form
self.cells = PersistentMapping() self.cells = PersistentMapping()
# the page range we want to print
self.__page_range__ = ""
# the method to format values
self.__format_method__ = ""
if not pdf_file : # File constructor will set the file content
raise ValueError ("The pdf form file should not be empty")
# File constructor will call manage_upload, so we don't need to call it
File.__init__(self, id, title, pdf_file) File.__init__(self, id, title, pdf_file)
security.declareProtected(Permissions.ManagePortal, 'manage_upload') security.declareProtected(Permissions.ManagePortal, 'manage_upload')
...@@ -389,7 +400,7 @@ class PDFForm(File): ...@@ -389,7 +400,7 @@ class PDFForm(File):
return return
raise ValueError, "Unable to download from any url from the "\ raise ValueError, "Unable to download from any url from the "\
"`download_url` property." "`download_url` property."
security.declareProtected(Permissions.ManagePortal, security.declareProtected(Permissions.ManagePortal,
'deletePdfContent') 'deletePdfContent')
def deletePdfContent(self) : def deletePdfContent(self) :
...@@ -425,7 +436,7 @@ class PDFForm(File): ...@@ -425,7 +436,7 @@ class PDFForm(File):
return pdf return pdf
security.declareProtected(Permissions.ManagePortal, 'doEditCells') security.declareProtected(Permissions.ManagePortal, 'doEditCells')
def doEditCells(self, REQUEST): def doEditCells(self, REQUEST, RESPONSE=None):
""" This is the action to the 'Edit Cell TALES' tab. """ """ This is the action to the 'Edit Cell TALES' tab. """
if SUPPORTS_WEBDAV_LOCKS and self.wl_isLocked(): if SUPPORTS_WEBDAV_LOCKS and self.wl_isLocked():
raise ResourceLockedError, "File is locked via WebDAV" raise ResourceLockedError, "File is locked via WebDAV"
...@@ -434,12 +445,9 @@ class PDFForm(File): ...@@ -434,12 +445,9 @@ class PDFForm(File):
self.setCellTALES(k, REQUEST.get(str(k), v)) self.setCellTALES(k, REQUEST.get(str(k), v))
self.__format_method__ = REQUEST.get("__format_method__") self.__format_method__ = REQUEST.get("__format_method__")
self.__page_range__ = REQUEST.get("__page_range__") self.__page_range__ = REQUEST.get("__page_range__")
message = "Saved changes." if RESPONSE:
if getattr(self, '_v_warnings', None): return self.manage_cells(manage_tabs_message="Saved changes.")
message = ("<strong>Warning:</strong> <i>%s</i>"
% '<br>'.join(self._v_warnings))
return self.manage_cells(manage_tabs_message=message)
security.declareProtected(Permissions.View, 'generatePDF') security.declareProtected(Permissions.View, 'generatePDF')
def generatePDF(self, REQUEST=None, RESPONSE=None, *args, **kwargs) : def generatePDF(self, REQUEST=None, RESPONSE=None, *args, **kwargs) :
...@@ -450,8 +458,7 @@ class PDFForm(File): ...@@ -450,8 +458,7 @@ class PDFForm(File):
context = { 'here' : self.aq_parent, context = { 'here' : self.aq_parent,
'context' : self.aq_parent, 'context' : self.aq_parent,
'request' : REQUEST } 'request' : REQUEST }
if hasattr(self, "__format_method__") \ if self.__format_method__:
and self.__format_method__ not in ('', None) :
compiled_tales = getEngine().compile(self.__format_method__) compiled_tales = getEngine().compile(self.__format_method__)
format_method = getEngine().getContext(context).evaluate(compiled_tales) format_method = getEngine().getContext(context).evaluate(compiled_tales)
# try to support both method name and method object # try to support both method name and method object
...@@ -465,7 +472,7 @@ class PDFForm(File): ...@@ -465,7 +472,7 @@ class PDFForm(File):
'format method (%r) is not callable' % format_method) 'format method (%r) is not callable' % format_method)
data = str(self.data) data = str(self.data)
pdf = self.pdftk.fillFormWithDict(data, values) pdf = self.pdftk.fillFormWithDict(data, values)
if self.__page_range__ not in ('', None) : if self.__page_range__:
compiled_tales = getEngine().compile(self.__page_range__) compiled_tales = getEngine().compile(self.__page_range__)
page_range = getEngine().getContext(context).evaluate(compiled_tales) page_range = getEngine().getContext(context).evaluate(compiled_tales)
if page_range : if page_range :
...@@ -517,8 +524,8 @@ class PDFForm(File): ...@@ -517,8 +524,8 @@ class PDFForm(File):
uncalculated_values.remove(cell_name) uncalculated_values.remove(cell_name)
values[cell_name] = value values[cell_name] = value
if len(uncalculated_values) == uncalculated_values_len : if len(uncalculated_values) == uncalculated_values_len :
raise ValueError, "Circular reference: unable to evaluate cells " \ raise CircularReferencyError("Unable to evaluate cells: %r"
+ `uncalculated_values` % (uncalculated_values, ))
security.declareProtected(Permissions.View, 'getCellNames') security.declareProtected(Permissions.View, 'getCellNames')
def getCellNames(self, REQUEST=None) : def getCellNames(self, REQUEST=None) :
...@@ -579,9 +586,6 @@ class PDFForm(File): ...@@ -579,9 +586,6 @@ class PDFForm(File):
security.declareProtected(Permissions.View, 'getFormatMethodTALES') security.declareProtected(Permissions.View, 'getFormatMethodTALES')
def getFormatMethodTALES(self): def getFormatMethodTALES(self):
""" returns the TALES expression for the format method attribute """ """ returns the TALES expression for the format method attribute """
# backward compat
if not hasattr(self, "__format_method__") :
self.__format_method__ = ""
return self.__format_method__ return self.__format_method__
security.declareProtected(Permissions.ManagePortal, 'setFormatMethodTALES') security.declareProtected(Permissions.ManagePortal, 'setFormatMethodTALES')
......
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved.
# Jerome Perrin <jerome@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import unittest
import os
from Products.ERP5Form.PDFForm import PDFForm
from Products.ERP5.Document.Document import Document
class TestPDFForm(unittest.TestCase):
"""Tests PDF Form
"""
def setUp(self):
"""Creates a PDFForm, and a document on which the PDF form is rendered.
"""
self.document = Document('doc_id', title='The Document')
pdf_file = open(os.path.join(os.path.dirname(__file__),
'data', 'test_1.pdf'))
self.pdf_form = PDFForm('test_pdf_form').__of__(self.document)
self.pdf_form.manage_upload(pdf_file)
def test_getCellNames(self):
self.assertEquals(['text_1', 'text_2', 'text_3'],
self.pdf_form.getCellNames())
def test_SimpleGeneratePDF(self):
self.pdf_form.setCellTALES('text_1', 'string:Something simple')
self.failUnless(self.pdf_form.generatePDF())
# aliases
self.failUnless(self.pdf_form.index_html())
self.failUnless(self.pdf_form())
def test_EmptyGeneratePdf(self):
self.failUnless(self.pdf_form.generatePDF())
# aliases
self.failUnless(self.pdf_form.index_html())
self.failUnless(self.pdf_form())
def test_showCellName(self):
self.failUnless(self.pdf_form.showCellNames())
def test_CellTALES(self):
self.pdf_form.setCellTALES('text_1', 'here/getId')
self.assertEquals('here/getId', self.pdf_form.getCellTALES('text_1'))
def test_setInvalidTALES(self):
from Products.PageTemplates.TALES import CompilerError
self.pdf_form.setCellTALES('text_1', 'python:(inv.alid "= ')
# maybe should raise when setting the TALES, not when getting ?
self.assertRaises(CompilerError, self.pdf_form.evaluateCell, 'text_1')
def test_EditCells(self):
self.pdf_form.doEditCells(REQUEST=dict(text_1='here/getId',
text_2='string:'))
self.assertEquals('here/getId', self.pdf_form.getCellTALES('text_1'))
self.assertEquals('string:', self.pdf_form.getCellTALES('text_2'))
def test_EvaluateCell(self):
self.pdf_form.setCellTALES('text_1', 'here/getId')
self.assertEquals('doc_id', self.pdf_form.evaluateCell('text_1'))
def test_EvaluateNonExistCell(self):
self.assertRaises(KeyError, self.pdf_form.evaluateCell,
'this_cell_does_not_exist')
def test_CalculateCellValues(self):
self.pdf_form.setCellTALES('text_1', 'here/getId')
self.pdf_form.setCellTALES('text_2', 'string:static')
calculated_values = self.pdf_form.calculateCellValues()
self.assertEquals('doc_id', calculated_values['text_1'])
self.assertEquals('static', calculated_values['text_2'])
def test_CalculateCellValuesWithCellKey(self):
self.pdf_form.setCellTALES('text_1', 'here/getId')
self.pdf_form.setCellTALES('text_2', 'cell/text_1')
calculated_values = self.pdf_form.calculateCellValues()
self.assertEquals('doc_id', calculated_values['text_1'])
self.assertEquals('doc_id', calculated_values['text_2'])
def test_CalculateCellValuesTotal(self):
# The original use case of `cell`
self.pdf_form.setCellTALES('text_1', 'python:3')
self.pdf_form.setCellTALES('text_2', 'python:2')
self.pdf_form.setCellTALES('text_3',
'python:cell["text_1"] + cell["text_2"]')
self.assertEquals(3 + 2, self.pdf_form.calculateCellValues()['text_3'])
def test_CalculateCellValuesCircularRefs(self):
self.pdf_form.setCellTALES('text_1', 'cell/text2')
self.pdf_form.setCellTALES('text_2', 'cell/text_1')
from Products.ERP5Form.PDFForm import CircularReferencyError
self.assertRaises(CircularReferencyError,
self.pdf_form.calculateCellValues)
def test_CalculateCellValuesParms(self):
self.pdf_form.setCellTALES('text_1', 'a_parameter')
calculated_values = self.pdf_form.calculateCellValues(a_parameter='Value')
self.assertEquals('Value', calculated_values['text_1'])
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestPDFForm))
return suite
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment