[doc] Updated doc. Ready for publishing a new version of appyframework.org (at least I think :))

This commit is contained in:
Gaetan Delannay 2012-12-03 16:18:24 +01:00
parent ed3a31ff29
commit 6061060c49
48 changed files with 259 additions and 294 deletions

View file

@ -7,7 +7,7 @@
<body>
<h1>What is gen ?</h1>
<p><b>gen</b> is a code <b>gen</b>erator that allows you write web apps without having to face and understand the plumbery of a given web framework. <b>gen</b> protects you. Concentrate on functionalities that need to be implemented: <b>gen</b> will fight for you against the low-level twisted machineries and will let you evolve in your pure, elegant and minimalistic Python world.</p>
<p><b>gen</b> is a code <b>gen</b>erator that allows you write web apps without having to face and understand the plumbery of a given web framework. Concentrate on functionalities that need to be implemented: <b>gen</b> protects you from the complexity of low-level twisted machineries and will let you evolve in your pure, elegant and minimalistic Python world.</p>
<h1>Download and install</h1>
@ -23,7 +23,7 @@
./install.sh
</p>
<p>Don't be afraid, you just installed a lot of lines of code, but we will only use a very small subset of it: absolutely no line from Plone, a tiny subset of Zope and the Python interpreter that was also included.</p><br/>
<p>You just installed a lot of (deprecated) lines of code, but we will only use a very small subset of it: absolutely no line from Plone, a tiny subset of Zope and the Python interpreter that was also included.</p><br/>
<p>Install Appy. Download it <a href="https://launchpad.net/appy">here</a>, unzip it and install it in the Python interpreter previously mentioned.</p>
@ -83,12 +83,10 @@
&nbsp;&nbsp;&nbsp;&nbsp;companyDescription = String(format=String.XHTML, **p)<br/>
# ------------------------------------------------------------------------------<br/>
</p>
</body>
</html>
<p>This class is declared as <span class="code">root</span>: for the moment, just remember that it gives it a special, first-class status. One line below, we define the roles that are allowed to create instances of this class. <span class="code">Anonymous</span> and <span class="code">Manager</span> are 2 standard Appy roles. The remaining lines define the attributes of every registration. Dict <span class="code"><i>p</i></span> is simply a shorthand for specifying the same (group of) attribute(s) to several methods.</p><br/>
<p>Now that we have developed a complete webapp (yes, it is already over! Be patient, in a second iteration we will improve it), we need to plug it into the Zope instance.</p>
<p>Now that we have developed a complete webapp, we need to plug it into the Zope instance.</p>
<h1>Plug the webapp into the Zope instance</h1>
@ -150,7 +148,7 @@
<p align="center"><img src="img/welcome.png"/></p><br/>
<p>Congratulations! Now, let's create a new registration by clicking on the "plus" icon. You should get this form:</p>
<p>Congratulations! Now, without logging in (as anonymous user), let's create a new registration by clicking on the "plus" icon. You should get this form:</p>
<p align="center"><img src="img/newRegistration1.png"/></p>
<p align="center">...</p>
@ -164,9 +162,10 @@
<p>In this example, I have entered an email with a wrong format (remember, a validator <span class="code">String.EMAIL</span> was defined) and I entered no value for a mandatory field. <span class="code">multiplicity=(1,1)</span> means: at least and at most one value is required.</p><br/>
<p>Once validation succeeds, the registration is stored in the database and the user can visualize it:</p>
<p>Once validation succeeds, the registration is stored in the database and the user can visualize it.</p>
<p align="center"><img src="img/viewRegistration.png"/></p><br/>
<p>You can also log in as admin (password: admin) to discover the standard screens that are available, in your app, for an administrator.</p>
</body>
</html>

View file

@ -4,11 +4,13 @@
<link rel="stylesheet" href="appy.css" type="text/css">
</head>
<body>
<div class="focus" align="center">This section contains some deprecated information. Documentation is currently being rewritten and will soon be available.</div>
<h1><a name="classInheritance"></a>Class inheritance</h1>
<p>A gen-class may inherit from another gen-class. Suppose you work as director of human resources in a company and you need to measure employee productivity. Incredible but true: you are also a Python programmer. So you start developing a small application for managing employees. Although you are a director, you have reached a high level of wisdom (maybe due to the fact that you program in Python). So you know that there are two kinds of employees: those that work and those that don't. You decide to create 2 different classes to reflect this:</p>
<p class="code">
<p class="code codePara">
<b>class</b> Person:<br/>
&nbsp;&nbsp;root = True<br/>
&nbsp;&nbsp;title = String(show=False)<br/>
@ -36,7 +38,7 @@
<p>After a while, you become anxious about having 95% of the data in your database (=<span class="code">Person</span> instances) serving absolutely no purpose. Logically, you decide to register the hair color for every non-worker. You realize that you need to change your model to be able to do this:</p>
<p class="code">
<p class="code codePara">
<b>class</b> Person:</br>
&nbsp;&nbsp;abstract = True</br>
&nbsp;&nbsp;title = String(show=False)</br>
@ -64,7 +66,7 @@
<p>After a while, you become so excited about encoding hair colors that you would like to encode it even before encoding a parasite's name and first name. You have noticed that with gen, order of fields within the "edit" and "consult" views follows order of field declarations in the corresponding gen-classes; furthermore, fields defined in child classes appear after the fields defined in parent classes. Fortunately, the "move" parameter allows to change this default setting. Changing the <span class="code">Parasite</span> class this way produces the desired result:</p>
<p class="code">
<p class="code codePara">
<b>class</b> Parasite(Person):<br/>
&nbsp;&nbsp;root = True<br/>
&nbsp;&nbsp;hairColor= String(move=-2)<br/>
@ -76,7 +78,7 @@
<p>When defining a gen-class, some method names are reserved. Until now, we have already encountered the method <b class="code">onEdit</b>, like in this example:</p>
<p class="code">
<p class="code codePara">
<b>class</b> Person:</br>
&nbsp;&nbsp;abstract = True</br>
&nbsp;&nbsp;title = String(show=False)</br>
@ -90,7 +92,7 @@
<p>Another special method is named <b class="code">validate</b>. While the field-specific <span class="code">validators</span> are used for validating field values individually, the <span class="code">validate</span> method allows to perform "inter-field" validation when all individual field validations have succeeded. Consider this extension of class <span class="code">Parasite</span>:</p>
<p class="code">
<p class="code codePara">
<b>class</b> Parasite(Person):<br/>
&nbsp;&nbsp;root = True<br/>
&nbsp;&nbsp;hairColor= String(move=-2)<br/>
@ -162,7 +164,7 @@
<p>What you need to know when using pod with gen is the exact <i>pod context</i> (ie the set of Python objects, variables, modules, etc) that is given by gen to your pod template. The table below presents all entries included in it.</p>
<table>
<br/><table class="list">
<tr>
<th>Entry</th>
<th>Description</th>
@ -185,6 +187,7 @@
<td>A string containing the absolute path of the folder where your gen-application resides on disk. If your gen-application is a folder hierarchy, <span class="code">projectFolder</span> is the root folder of it.</td>
</tr>
</table>
<br/>
<p>In the previous examples, we have always rendered documents in <span class="code">Odt</span> format. When generating ODT, gen and pod do not need any other piece of software. If you configure a template for generating documents in <b>Adobe PDF (<span class="code">Pdf</span>)</b>, <b>Rich Text Format (<span class="code">Rtf</span>)</b> or <b>Microsoft Word (<span class="code">Doc</span>)</b>, you will need to run OpenOffice in server mode. Indeed, for producing any of these formats, pod will generate, from your POD template, an ODT file and will ask OpenOffice to convert it into PDF, DOC or RTF. Suppose you modify the ODT template named "Gossips" for producing documents in PDF instead of ODT (in the config, default flavour, tab "document generation", edit the template named "gossips" and choose "Pdf" as "Pod format"). If now you go back to the consult view for a parasite, the PDF icon will show up for the template "Gossips":</p>
@ -214,7 +217,7 @@
<p>Let's consider the following example. It is an improved version of the Human Resources software developed by yourself several minutes ago (see above). I have added more fields in order to illustrate how to layout fields into pages and groups.</p>
<p class="code">
<p class="code codePara">
<b>class</b> Person:<br/>
&nbsp;&nbsp;abstract = True<br/>
&nbsp;&nbsp;pod = True<br/>
@ -393,14 +396,14 @@
<p>Let's illustrate it first by defining a custom <span class="code">Tool</span>. We will use our <span class="code">ZopeComponent</span> example. Suppose that the company that creates those components is organized into bunches of geeks. Every component is developed under the responsibility of a bunch. We first need a class for defining bunches:</p>
<p class="code">
<p class="code codePara">
<b>class</b> BunchOfGeek:<br/>
&nbsp;&nbsp;description = String(format=String.TEXT)<br/>
</p>
<p>Creating a custom tool is as simple as inheriting from class <spanc class="code">appy.gen.Tool</span>:</p>
<p class="code">
<p class="code codePara">
<b>class</b> ZopeComponentTool(Tool):<br/>
&nbsp;&nbsp;someUsefulConfigurationOption = String()<br/>
&nbsp;&nbsp;bunchesOfGeeks = Ref(BunchOfGeek, multiplicity=(0,None), add=True,<br/>
@ -412,7 +415,7 @@
<p>Now please modify class <span class="code">ZopeComponent</span> by adding a <span class="code">Ref</span> attribute that will allow to assign the component to a given bunch:</p>
<p class="code">
<p class="code codePara">
&nbsp;&nbsp;responsibleBunch = Ref(BunchOfGeek, multiplicity=(1,1), add=False,<br/>
&nbsp;&nbsp;&nbsp;&nbsp;link=True, back=Ref(attribute='components'))<br/>
</p>
@ -433,7 +436,7 @@
<p>We will now transform our example in order to define bunches of geeks at the flavour level. First, delete from your tool the 3 bunches you have created. Then, move attribute <span class="code">bunchesOfGeeks</span> from your custom tool to a new custom Flavour:</p>
<p class="code">
<p class="code codePara">
<b>class</b> ZopeComponentTool(Tool):<br/>
&nbsp;&nbsp;someUsefulConfigurationOption = String()<br/>
<br/>
@ -452,7 +455,7 @@
<p>As you may have noticed, the default <span class="code">Tool</span> class defines only one page (the <span class="code">main</span> page). Feel free to add pages to your custom tool. The default <span class="code">Flavour</span> class defines the following pages; within your custom flavour, you may either add fields to those existing pages (like in the previous example) or add your own pages.</p>
<table>
<br/><table class="list" align="center">
<tr>
<th>Page name</th>
<th>Description</th>
@ -474,6 +477,7 @@
<td>Configuration options related to the way your gen-application looks. Columns displayed in the dashboards are configured here for example.</td>
</tr>
</table>
<br/>
<p>When defining fields on custom tools and flavours, some parameters have no sense. This is the case for all parameters enabling "configurability", like <span class="code">editDefault</span>, <span class="code">optional</span>, etc: you can't meta-configure (=configure the configuration). If you try to use those parameters on fields defined on custom tools and flavour they will be ignored by gen.</p>
@ -495,7 +499,7 @@
<p>Pragmatically, what you need to know is the following. For every Appy field defined in a gen-class, the corresponding wrapper class contains a Python <i>property</i> (more info <a href="http://docs.python.org/library/functions.html?highlight=property#property" target="_blank">here</a>) that has the same name, for which a <i>getter</i> function is defined. Every time you write a thing like:</p>
<p class="code">self.bunchesOfGeeks</p>
<p class="code codePara">self.bunchesOfGeeks</p>
<p>The getter function is triggered behind the scenes and queries the real Zope object for getting the expected result. After it, the function may potentialy adapt the result before giving it to you. In this example, every "Zope" bunch of geeks is wrapped and you get a list of <span class="code">BunchOfGeek</span> wrappers. This way, you always manipulate wrappers and you may forget everything about the cruel Zope world.</p>
@ -515,7 +519,7 @@
<p>Beyond getters and setters, Appy wrappers give you plenty of nice attributes and methods for manipulating your objects. The following table shows you available attributes (or Python properties).</p>
<table>
<br/><table class="list" align="center">
<tr>
<th>field name</th>
<th>writable?</th>
@ -557,10 +561,11 @@
<td>The Python class (=gen-class) related to this class. Indeed, because the instances you manipulate in the code inherit from special wrappers, writing <span class="code">self.__class__</span> will give you a wrapper class. So write <span class="code">self.klass</span> instead.</td>
</tr>
</table>
<br/>
<p>The following table shows you available methods.</p>
<table>
<br/><table class="list" align="center">
<tr>
<th>method name</th>
<th>parameters</th>
@ -570,7 +575,7 @@
<td class="code">create</td>
<td class="code">fieldName, **kwargs</td>
<td>Creates a new object and links it to this one through <span class="code">Ref</span> field having name <span class="code">fieldName</span>. Remaining parameters <span class="code">**kwargs</span> are used for initialising fields of the newly created object. Here is an example, inspired from the one described above. Suppose that, in every flavour, a bunch of geeks called "Escadron de la mort" must absolutely be present: it includes the best geeks that you use for debugging the most critical Zope components. So every time you create or edit a flavour, you need to ensure that this bunch is there. If not, you will create it automatically. Code for class <span class="code">ZopeComponentFlavour</span> must evolve this way:
<p class="code">
<p class="code codePara">
<b>class</b> ZopeComponentFlavour(Flavour):<br/>
&nbsp;&nbsp;anIntegerOption = Integer()<br/>
&nbsp;&nbsp;bunchesOfGeeks = Ref(BunchOfGeek, multiplicity=(0,None), add=True,<br/>
@ -590,6 +595,7 @@
<td>Links the existing object <span class="code">obj</span> to the current one through <span class="code">Ref</span> field <span class="code">fieldName</span>. Already linked objects are not unlinked. This method is used by method <span class="code">create</span> but may also be used independently.</td>
</tr>
</table>
<br/>
<p>For setting <span class="code">Ref</span> fields, please use the <span class="code">create</span> and <span class="code">link</span> methods instead of the predefined setters which may not work as expected.</p>
@ -597,7 +603,7 @@
<p><span class="code">Tool</span> and <span class="code">Flavour</span> objects, be they customized or not, adopt the same "wrapper" mechanism as described above. In this section, we will simply explain how to get/set programmatically the predefined fields that gen generates automatically on tools and flavours. The following table presents the names of the predefined attributes defined on any <span class="code">Tool</span>:</p>
<table>
<br/><table class="list" align="center">
<tr>
<th>field name</th>
<th>description</th>
@ -627,10 +633,11 @@
<td>The boolean indicating if the field for entering comments when triggering a worflow transition must be shown or not.</td>
</tr>
</table>
<br/>
<p>For flavours, things are a little more complex. Imagine we have, throughout our gen-application, 25 fields parameterized with <span class="code">editDefault=True</span> in 6 classes. The corresponding attributes that hold the default values in the flavours have an ugly name that includes the full package name of the class. Instead of forcing you to remember this obscure naming convention, a nice method defined on the <span class="code">Flavour</span> class allows to retrieve this attribute name:</p>
<table>
<br/><table class="list" align="center">
<tr>
<th>method name</th>
<th>parameters</th>
@ -669,10 +676,11 @@
</td>
</tr>
</table>
<br/>
<p>Besides flavour attributes having oscure names, some attributes have a normal name:</p>
<table>
<br/><table class="list" align="center">
<tr>
<th>field name</th>
<th>writable?</th>
@ -684,6 +692,7 @@
<td>The flavour number.</td>
</tr>
</table>
<br/>
<p>When choosing field and method names for your gen-classes, try to avoid using names corresponding to fields or methods from base Appy classes (wrappers, tool, flavours).</p>
@ -691,7 +700,7 @@
<p>When (re-)installing your gen-application, you may want to initialize it with some data or configuration options. You do this by specifying a special <a href="genCreatingBasicClasses.html#actions">action</a> named <span class="code">install</span> on your customized tool, like in the example below (the <span class="code">ZopeComponent</span> application).</p>
<p class="code">
<p class="code codePara">
<b>class</b> ZopeComponentTool(Tool):<br/>
&nbsp;&nbsp;someUsefulConfigurationOption = String()<br/>
&nbsp;&nbsp;<b>def</b> onInstall(self):<br/>
@ -715,7 +724,7 @@
<p>gen-applications benefit from an automated support for i18n. How does it work? First of all, you need to declare what language(s) need to be supported in your gen-application. This is done through the creation, anywhere in your gen-application, of an instance of class <span class="code">appy.gen.Config</span>:</p>
<p class="code">
<p class="code codePara">
<b>from</b> appy.gen <b>import</b> Config<br/>
c = Config()<br/>
c.languages = ('en', 'fr')<br/>
@ -725,7 +734,7 @@
<p>Every time you (re-)generate your gen-application, i18n files are created or updated in the corresponding generated Plone product. With the above settings, gen will generate the following files, in <span class="code">[yourZopeInstance]/Products/[yourApplication]/i18n</span> (the product here is named <span class="code">ZopeComponent</span>):</p>
<p class="code">
<p class="code codePara">
ZopeComponent.pot<br/>
ZopeComponent-en.po<br/>
ZopeComponent-fr.po<br/>
@ -735,7 +744,7 @@
<p>The format of these files is quite standard in the i18n world. Le'ts take a look to the beginning of <span class="code">ZopeComponent.pot</span>:</p>
<p class="code">
<p class="code codePara">
<b>msgid</b> ""<br/>
<b>msgstr</b> ""<br/>
"Project-Id-Version: ZopeComponent\n"<br/>
@ -754,7 +763,7 @@
<p>After this header, you will find a list of <i>labels</i>. In the <span class="code">pot</span> file, every label is defined like this one:</p>
<p class="code">
<p class="code codePara">
#. Default: "Funeral date"<br/>
<b>msgid</b> "ZopeComponent_ZopeComponent_funeralDate"<br/>
<b>msgstr</b> ""<br/>
@ -768,7 +777,7 @@
<p>Now, you need to know the meaning of every label gen creates and maintains in the po(t) file(s), and at what place(s) they are used within the generated edit and consult views (or in the dashboards, etc). The following table explains this. Dynamic parts used in labels (like <span class="code">[className]</span>) are explained in yet another table below. For every label, the default value is specified. For the majority of labels, gen proposes a "nice" default value. For example, field <span class="code">responsibleBunch</span> will have default value <span class="code">Responsible bunch</span>; class <span class="code">BunchOfGeeks</span> will have default value <span class="code">Bunch of geeks</span>. The "nice" algorithm tries simply to recognize camel-cased words and separates them.</p>
<table>
<br/><table class="list" align="center">
<tr>
<th>label "pattern"</th>
<th>usage</th>
@ -949,10 +958,11 @@
<td>"Please enter a valid alphanumeric value."</td>
</tr>
</table>
<br/>
<p>As already mentioned, in the table below, some label "parts" are explained.</p>
<table>
<br/><table class="list" align="center">
<tr>
<th>label part</th>
<th>description</th>
@ -974,12 +984,13 @@
<td>Refers to the name of an Appy field, declared as a static attribute in a gen-class. For example, attribute <span class="code">responsibleBunch</span> of class <span class="code">ZopeComponent</span> will have <span class="code">fieldName</span> "responsibleBunch".</td>
</tr>
</table>
<br/>
<h2>Creating and using new i18n labels</h2>
<p>Although gen tries to manage automatically the whole i18n thing, in some cases you may need to create and use specific labels. You create new labels by adding them "by hand" in the pot file. For example, edit ZopeComponent.pot and add the following label:</p>
<p class="code">
<p class="code codePara">
#. Default: "Please be honest and do not pretend there is any kind of simplicity in your Zope 3 component."<br/>
<b>msgid</b> "zope_3_is_not_simple"<br/>
<b>msgstr</b> ""
@ -989,7 +1000,7 @@
<p>In the following example, we use the new label for producing a translated validation message on a given field.</p>
<p class="code">
<p class="code codePara">
<b>class</b> ZopeComponent:
&nbsp;&nbsp;...<br/>
&nbsp;&nbsp;<b>def</b> validateDescription(self, value):<br/>
@ -1005,7 +1016,7 @@
<p>Now imagine you need to change the default value for this label in ZopeComponent.pot. It means that your translations in ZopeComponent-fr.po and ZopeComponent-en.po need a potential revision. Edit the default value and re-generate your product. The labels in the po files will be flagged as "fuzzy":</p>
<p class="code">
<p class="code codePara">
#. Default: "Please be honest and do not pretend there is any kind of simplicity in your Zope 3 component. I changed the default value."<br/>
#, fuzzy<br/>
<b>msgid</b> "zope_3_is_not_simple"<br/>
@ -1018,7 +1029,7 @@
<p>It is possible to make your gen pages more dynamic by defining relationships between fields belonging to the same page. For example, by selecting a given item in a list (on a <i>master</i> field), another (<i>slave</i>) field may become visible. This is achieved by using parameters <span class="code">master</span> and <span class="code">masterValue</span>. Let's try it on an example. Remember that, in our HR system, we have added the ability to associate a "parasite index" to a parasite. Suppose that in some cases, the parasite is so weird that you are unable to give him a parasite index. Among the values for field <span class="code">parasiteIndex</span>, we add the value <span class="code">unquantifiable</span>; but in this case, you must give, in another field, details about why you can't quantify its parasiteness:</p>
<p class="code">
<p class="code codePara">
<b>class</b> Parasite(Person):<br/>
&nbsp;&nbsp;...<br/>
&nbsp;&nbsp;parasiteIndex = String(validator=['low', 'medium', 'high', 'unquantifiable'],<br/>
@ -1060,7 +1071,7 @@
<p>As already mentioned, a series of configuration options for your gen-application may be defined in an instance of class <span class="code">appy.gen.Config</span>. There must be only one such instance by gen-application. The following table describes the available options defined as <span class="code">Config</span> attributes.</p>
<table>
<br/><table class="list" align="center">
<tr>
<th>Attribute</th>
<th>Default value</th>
@ -1082,10 +1093,11 @@
<td>If <span class="code">True</span>, this flag will produce a minimalist Plone, where some user interface elements, like actions, portlets or other stuff less relevant for building web applications, are removed or hidden. Using this produces effects on your whole Plone site! This can be interesting on development machines: less visual gadgets make geeks more productive.</td>
</tr>
</table>
<br/>
<p>Here is an example of a <span class="code">Config</span> instance:</p>
<p class="code">
<p class="code codePara">
<b>from</b> appy.gen <b>import</b> Config<br/>
c = Config()<br/>
c.languages = ('en', 'fr')<br/>

View file

@ -4,6 +4,8 @@
<link rel="stylesheet" href="appy.css" type="text/css">
</head>
<body>
<div class="focus" align="center">This section contains some deprecated information. Documentation is currently being rewritten and will soon be available.</div>
<h1><a name="genApplications"></a>Gen-applications</h1>
<p>A gen-application is a simple Python module (a single Python file) or package (a hierarchy of folders where every (sub-)folder contains a file named __init__.py). In the <a href="gen.html">main gen presentation page</a>, we've created a simple application in the form of a Python module, we've run it by installing Zope and Plone and by generating a Plone product. Working with a Python package instead of a Python module is quite easy: instead of creating MyModule.py in <a class="code">[myZopeInstance]/lib/python</a> you simply create a folder "MyPackage" at the same place.</p>
@ -16,7 +18,7 @@
<p>The code below shows an example of a gen-class.</p>
<p class="code">
<p class="code codePara">
<b>from</b> appy.gen <b>import</b> *<br/>
<b>class</b> A:<br/>
&nbsp;&nbsp;at1 = String()<br/>
@ -28,9 +30,9 @@
&nbsp;&nbsp;&nbsp;&nbsp;<b>print</b> self.at5 + str(self.at2)<br/>
</p>
<p><span class="code">String</span>, <span class="code">Integer</span>, <span class="code">Float</span> and <span class="code">Date</span> all inherit from <span class="code">appy.gen.Type</span>. You may still define standard Python attributes like <span class="code">at5</span> and methods like <span class="code">sayHello</span>. The list of basic types provided by gen is shown below.</p>
<p><br/><span class="code">String</span>, <span class="code">Integer</span>, <span class="code">Float</span> and <span class="code">Date</span> all inherit from <span class="code">appy.gen.Type</span>. You may still define standard Python attributes like <span class="code">at5</span> and methods like <span class="code">sayHello</span>. The list of basic types provided by gen is shown below.</p>
<table class="appyTable">
<br/><table class="list" align="center">
<tr>
<td class="code">Integer&nbsp;</td>
<td>Holds an integer value.</td>
@ -68,9 +70,10 @@
<td>Holds nothing; the field represents a button or icon that triggers a function specified as a Python method.</td>
</tr>
</table>
<br/>
<p>When defining instances of those types, you will typically give some parameters to it. Below is the list of parameters that are common to all types. In the next sections, we will see into more detail every type with its specificities.</p>
<table class="appyTable">
<br/><table class="list" align="center">
<tr>
<th>parameter</th>
<th>default value</th>
@ -198,7 +201,7 @@
<p>Integers and floats have no additional parameters. In this section, we will simply illustrate, on Integers and Floats, some parameters defined in the previous section. Let's consider the following class:</p>
<p class="code"><b>from</b> appy.gen <b>import</b> *<br/>
<p class="code codePara"><b>from</b> appy.gen <b>import</b> *<br/>
<b>class</b> Zzz:<br/>
&nbsp;&nbsp;root = True<br/>
&nbsp;&nbsp;<b>def</b> show_f1(self): <b>return</b> True<br/>
@ -236,9 +239,9 @@
<h1><a name="strings"></a>Strings</h1>
<p>Strings have an additional attribute named <span class="code">format</span> which may take the following values:</p>
<p>Strings have an additional attribute named <span class="code">format</span> which may take the following values</p>
<table class="appyTable">
<br/><table class="list" align="center">
<tr>
<th>value</th>
<th>default?</th>
@ -268,10 +271,11 @@
<td><img src="img/strings6.png"></td>
</tr>
</table>
<br/>
<p>With <span class="code">String</span>s, adequate use of arguments <span class="code">validator</span> and <span class="code">multiplicity</span> may produce more widgets and/or behaviours. Consider the following class:</p>
<p class="code">
<p class="code codePara">
<b>class</b> SeveralStrings:<br/>
&nbsp;&nbsp;root=True<br/>
&nbsp;&nbsp;anEmail = String(validator=String.EMAIL)<br/>
@ -311,7 +315,7 @@
<p>Dates have an additional attribute named <span class="code">format</span> which may take the following values:</p>
<table class="appyTable">
<table class="list" align="center">
<tr>
<th>value</th>
<th>default?</th>
@ -371,7 +375,7 @@
<p>The corresponding gen model looks like this:</p>
<p class="code">
<p class="code codePara">
<b>class</b> Order:<br/>
&nbsp;&nbsp;description = String(format=String.TEXT)<br/>
<br/>
@ -415,7 +419,7 @@
<p>Besides the "add" functionality, association ends may also provide the "link" functionality. This produces another widget that allows you to link an object to existing ones instead of creating + linking them. Suppose you extend you model with the concept of <span class="code">Product</span>: an order may now specify one or several products. The model would include the new <span class="code">Product</span> class and the <span class="code">Order</span> class would get an additional <span class="code">Ref</span> field:</p>
<p class="code">
<p class="code codePara">
<b>class</b> Product:<br/>
&nbsp;&nbsp;root = True<br/>
&nbsp;&nbsp;description = String(format=String.TEXT)<br/>
@ -452,7 +456,7 @@
<p>For references, 2 more parameters allow to customize the way they are rendered: the boolean <span class="code">showHeaders</span> and <span class="code">shownInfo</span>. Let's consider again the consult view for a client (5 pictures above: gold client "Gaetan Delannay"). Beyond order's titles, I would like to display their description, too, in another column. But If I have several columns, it would be nice to get some columns headers. You may achieve the desired result by changing the definition of the field <span class="code">orders</span> this way:</p>
<p class="code">
<p class="code codePara">
&nbsp;&nbsp;orders = Ref(Order, add=True, link=False, multiplicity=(0,None),<br/>
&nbsp;&nbsp;&nbsp;&nbsp;back=Ref(attribute='client'), showHeaders=True,<br/>
&nbsp;&nbsp;&nbsp;&nbsp;shownInfo=('description',))<br/>
@ -476,7 +480,7 @@
<p>When using <span class="code">shownInfo</span>, you may specify any field name, including <span class="code">Ref</span> fields. If you specify <span class="code">shownInfo=('description', 'products')</span> for the field <span class="code">orders</span> of class <span class="code">Client</span> and modify rendering of field <span class="code">products</span> from class <span class="code">Order</span> this way:</p>
<p class="code">
<p class="code codePara">
<b>class</b> Order:<br/>
&nbsp;&nbsp;description = String(format=String.TEXT)<br/>
&nbsp;&nbsp;products = Ref(Product, add=False, link=True, multiplicity=(1,None),<br/>
@ -498,7 +502,7 @@
<p>You may want to filter only some products instead of gathering all defined products. The <span class="code">select</span> parameter may be used for this. Here is an example:</p>
<p class="code">
<p class="code codePara">
<b>class</b> Order:<br/>
&nbsp;&nbsp;description = String(format=String.TEXT)<br/>
&nbsp;&nbsp;<b>def</b> filterProducts(self, allProducts):<br/>
@ -527,7 +531,7 @@
<p>Let's try it on our example. Suppose we want to produce nice references for orders, based on some random number (yes, it would have been better to use some incremental number: it it really easy to do this with gen, but you need to know how to customize the configuration panel, which is explained later). We need to define a field <span class="code">number</span> that will hold the order number and will be invisible. Then, we will define a <span class="code">Computed</span> field named <span class="code">reference</span> that will produce the reference based on some prefix and the order number. Class <span class="code">Order</span> need to be updated like this:</p>
<p class="code">
<p class="code codePara">
<b>class</b> Order:<br/>
&nbsp;&nbsp;...<br/>
&nbsp;&nbsp;number = Float(show=False)<br/>
@ -551,7 +555,7 @@
<p>Like any other field, <span class="code">Computed</span> fields may appear on <span class="code">Ref</span> fields or on dashboards. For example, if we change the definition of <span class="code">Ref</span> field <span class="code">orders</span> on class <span class="code">Client</span> this way:</p>
<p class="code">
<p class="code codePara">
<b>class</b> Client:<br/>
&nbsp;&nbsp;...<br/>
&nbsp;&nbsp;orders = Ref(Order, add=True, link=False, multiplicity=(0,None),<br/>
@ -569,7 +573,7 @@
<p>Actions are special fields that allow to trigger functions. For the moment, they are represented as buttons and are shown only on consult views (not on edit views). Let's take an example. Suppose we modify class <span class="code">Product</span> this way:</p>
<p class="code">
<p class="code codePara">
<b>class</b> Product:<br/>
&nbsp;&nbsp;root = True<br/>
&nbsp;&nbsp;description = String(format=String.TEXT)<br/>
@ -623,7 +627,7 @@
<p>This screenshot shows the ZMI (Zope Management Interface) available at http://localhost:8080/manage. You see that within the <span class="code">Appy</span> object (which is a Plone site) you have, among some predefined Plone objects like <span class="code">MailHost</span> or <span class="code">Members</span>, 2 folders named <span class="code">ZopeComponent</span> and <span class="code">Zzz</span>. Each of these 2 folders correspond to a gen-application that has the same name. Those folders are an Appy adaptation of the Plone standard content type named "Large Plone Folder", which is used for storing a large number of objects. If you click on this folder you will see its content (=all objects created through the corresponding gen-application). For several reasons, you may want to put more structure among this folder. Firstly, if you reach a large number of objects, it could lead to performance problems. Secondly, if you use the standard Plone "navigation" portlet, you will see in it all your objects in a single long and unstructured list under a single folder entry named according to your application. The solution is to tell gen that some classes are "folderish". You simply tell this by specifying <span class="code">folder=True</span> on your class. Suppose you do this on class <span class="code">Client</span>:</p>
<p class="code">
<p class="code codePara">
<b>class</b> Client:<br/>
&nbsp;&nbsp;root = True<br/>
&nbsp;&nbsp;folder = True<br/>

View file

@ -4,6 +4,9 @@
<link rel="stylesheet" href="appy.css" type="text/css">
</head>
<body>
<div class="focus" align="center">This section contains some deprecated information. Documentation is currently being rewritten and will soon be available.</div>
<h1><a name="principles"></a>The principles</h1>
<p>The security model behinds gen-applications is similar to what Zope and Plone offer; simply, gen tries to simplify the way to declare and manage security within your applications. According to this model, <b>users</b> are put into <b>groups</b>; groups have some <b>roles</b>; roles are granted basic <b>permissions</b> on objects (create, read, write, delete, etc). Permissions-to-roles mappings may vary according to the <b>state</b> of objects.</p>
@ -18,7 +21,7 @@
<p>Now, go to "Users and Groups Administration" (still in "Site setup") and add the following users using the button "add new user" (do not check "Send a mail with the password" and enter a dummy email):</p>
<table>
<br/><table class="list" align="center">
<tr>
<th>User Name</th>
<th>Password</th>
@ -32,12 +35,13 @@
<td>ludovic</th>
</tr>
</table>
<br/>
<p>Now, we need groups. Guess what? We will not create groups. Why? Because gen will generate groups automatically for you!</p>
<p>Now that we have users and groups, it is time to create roles. Guess what? We will not do it. Why? Because it is simply not needed. gen will scan your code and find every role you mention and will create them automatically at the Zope/Plone level if they do not exist yet. We will use the following roles:</p>
<table>
<br/><table class="list" align="center">
<tr>
<th>role name</th>
<th>description</th>
@ -55,10 +59,11 @@
<td>Zope/Python developer</td>
</tr>
</table>
<br/>
<p>gen will create one group for every role defined in your application; the group will be granted only the corresponding role. Note that we will probably not use the role <span class="code">ZDeveloper</span>. Indeed, developers work. They will probably not use a management tool. Now, let's tackle permissions. Again, it is not needed to create permissions (at least now): gen provides the following default permissions:</p>
<table>
<br/><table class="list" align="center">
<tr>
<th>name</th>
<th>corresponding code object</th>
@ -85,6 +90,7 @@
<td>Permission to delete an object</td>
</tr>
</table>
<br/>
<p>All the security ingredients are now ready (users, groups, roles and permissions): we will now see how to use them to define security on a gen-application.</p>
@ -92,7 +98,7 @@
<p>Permission to <span class="code">create</span> objects is done at 2 levels. First, you may define a global list of roles that will, by default, be allowed to create any object of any class in your gen-application. In our company, <span class="code">ZLeader</span>s are responsible for creating Zope components. You declare this global list in attribute <span class="code">defaultCreators</span> of your <span class="code">appy.gen.Config</span> instance introduced while <a href="genCreatingAdvancedClasses.html#i18n">presenting i18n</a>:</p>
<p class="code">
<p class="code codePara">
c = Config()<br/>
c.languages = ('en', 'fr')<br/>
c.defaultCreators += ['ZLeader']<br/>
@ -102,7 +108,7 @@
<p>Defining default creator roles for every class of your application may not be subtle enough. This is why gen allows you do it per class, with static attribute <span class="code">creators</span>. For example, you may use this attribute on class ZopeComponent:</p>
<p class="code">
<p class="code codePara">
class ZopeComponent:<br/>
&nbsp;&nbsp;...<br/>
&nbsp;&nbsp;creators = c.defaultCreators + ['ZLeader']<br/>
@ -118,7 +124,7 @@
<p>So let's define a simple workflow for our class <span class="code">ZopeComponent</span>. Until now our class looks like this:</p>
<p class="code">
<p class="code codePara">
<b>class</b> ZopeComponent:<br/>
&nbsp;&nbsp;root = True<br/>
&nbsp;&nbsp;<b>def</b> showDate(self):<br/>
@ -141,7 +147,7 @@
<p>Field <span class="code">status</span> seems to be a kind of workflow embryo. So we will remove it and create a workflow whose states will look like values of this field:</p>
<p class="code">
<p class="code codePara">
<b>class</b> ZopeComponentWorkflow:<br/>
&nbsp;&nbsp;<i># Roles</i><br/>
&nbsp;&nbsp;zManager = 'ZManager'<br/>
@ -230,7 +236,7 @@
<p>We have now finished our first security tour. An important remark is that we have granted roles "globally" to groups: any user of the group has always the globally granted role, under all circumstances, on any related object in your gen-application. In our example, Ludovic and all potential other project leaders have the right to edit all <span class="code">created</span> components. This may not be the desired behaviour. Maybe would you prefer any project leader to be able to edit his own components but not components created by other project leaders. This is where "local roles" come into play. A local role is a role that a user or group has, but only on a given object. The default Plone role "Owner" is an example of local role: this is not a role that you grant "globally" to a user or group (like the ones shown in tab "groups" or "users" of "Site setup -> Users and Groups Administration"); this is a role that is granted on an object to the user that created it. You may of course reference local roles within gen-workflows. For example, if you want to restrict component modifications to <span class="code">Owner</span>s and <span class="code">Manager</span>s when the component is <span class="code">created</span>, you may modify the workflow state <span class="code">created</span> like this:</p>
<p class="code">created = State({r:leaderM, w:('Owner', 'Manager'), d:leaderM}, initial=True)
<p class="code codePara">created = State({r:leaderM, w:('Owner', 'Manager'), d:leaderM}, initial=True)
</p>
<p>Re-generate your product and re-install it. The Plone procedure for re-installing a product updates the workflow definition but does not update the permissions-to-roles mappings defined on existing objects. In order to synchronize them with the new workflow definition, you need to go, through the ZMI, in object "portal_workflow" within your Plone site. At the bottom of the page, you will find a button "Update security settings". Click on it. This may take a long time if you have a large number of objects in your database. In future gen releases, you will be able to re-install your product directly from your tool. This specific procedure will ask you if you want to "Update workflow settings on existing objects" or not.</p>
@ -247,7 +253,7 @@
<p>Until now, we have seen that, as transition <span class="code">condition</span>, you can specify role(s) (one, as a string, or a tuple of roles). You can also specify Python method(s) the same way, and even mix roles and Python methods. Specified Python method(s) must belong to your gen-workflow (or one of its parents, yes, we will soon talk about workflow inheritance!). With such methods, more complex conditions may be defined. Let's show it by refining our previous example. Suppose that components can be <span class="code">validated</span> only if a funeral date (which is not a mandatory field) has been specified. Transition <span class="code">validate</span> need to evolve:</p>
<p class="code">
<p class="code codePara">
<b>class</b> ZopeComponentWorkflow:<br/>
&nbsp;&nbsp;...<br/>
&nbsp;&nbsp;<b>def</b> funeralOk(self, obj): <b>return</b> obj.funeralDate<br/>
@ -259,7 +265,7 @@
<p>One may also define action(s) (as Python method(s)) that are executed after any transition has been triggered. Let's suppose we want to reinitialise the component <span class="code">description</span> when we start its development. This is completely silly of course. But I like to force you doing silly things, it is a pleasant feeling. So let's update transition <span class="code">startDevelopment</span>:</p>
<p class="code">
<p class="code codePara">
<b>class</b> ZopeComponentWorkflow:<br/>
&nbsp;&nbsp;...<br/>
&nbsp;&nbsp;<b>def</b> updateDescription(self, obj):<br/>
@ -277,7 +283,7 @@
<p>Let's try it on our class <span class="code">ZopeComponent</span>. Suppose we need a specific write permission on field <span class="code">funeralDate</span> and a specific read permission on field <span class="code">responsibleBunch</span>:</p>
<p class="code">
<p class="code codePara">
<b>class</b> ZopeComponent:<br/>
&nbsp;&nbsp;...<br/>
&nbsp;&nbsp;funeralDate = Date(optional=True, specificWritePermission=True)<br/>
@ -289,7 +295,7 @@
<p>Now, in our workflow, for every state, we need to update the permissions-to-roles mapping by specifying the roles that will be granted those 2 new permissions. But first, we need a way to designate those permissions. This is done by using classes <span class="code">appy.gen.ReadPermission</span> and <span class="code">appy.gen.WritePermission</span> like this:</p>
<p class="code">
<p class="code codePara">
<b>class</b> ZopeComponentWorkflow:<br/>
&nbsp;&nbsp;<i># Specific permissions</i><br/>
&nbsp;&nbsp;wf = WritePermission('ZopeComponent.funeralDate')<br/>
@ -302,7 +308,7 @@
<p>Now let's update every state definition by integrating those 2 permissions in the permissions-to-roles mappings:</p>
<p class="code">
<p class="code codePara">
<b>class</b> ZopeComponentWorkflow:<br/>
&nbsp;&nbsp;...<br/>
&nbsp;&nbsp;<i># States</i><br/>
@ -331,7 +337,7 @@
<p>With gen, workflows are Python classes. This allows us to benefit from class inheritance and apply it to workflows. Our company that creates Zope components is now used to heavy technologies. They got a business revelation: some managers discovered that COBOL and Zope 3 had a lot in common on both philosophical and technical points of view. So they decided to begin creating components in COBOL. They were so excited about it that they needed to update their management software as quickly as possible. So a new class was added for registering information about COBOL components. The associated workflow was almost similar to the existing <span class="code">ZopeComponentWorkflow</span>; a new workflow inheriting from it was created:</p>
<p class="code">
<p class="code codePara">
<b>class</b> CobolComponentWorkflow(ZopeComponentWorkflow):<br/>
&nbsp;&nbsp;p = ZopeComponentWorkflow <i># Shortcut to workflow parent</i><br/>
&nbsp;&nbsp;<i># An additional state</i><br/>
@ -359,7 +365,7 @@
<p>As usual, for every workflow state and transition, i18n labels have been automatically generated (in the <span class="code">plone</span> domain), together with a "nice" default value. The format of those labels is defined <a href="genCreatingAdvancedClasses.html#i18n">here</a>. There is still a small problem with the <span class="code">CobolComponentWorkflow</span>: the transition for finishing the work is called <span class="code">cancelDevelopment</span>. I am too lazy for creating another transition, so I will simply modify here the translation of this transition in the corresponding i18n file (=ZopeComponent-plone-en.po in this case):</p>
<p class="code">
<p class="code codePara">
<i>#. Default: "Cancel development"</i><br/>
<b>msgid</b> "zopecomponent_cobolcomponentworkflow_cancelDevelopment"<br/>
<b>msgstr</b> "Finish"<br/>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 803 B

After

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View file

@ -6,34 +6,40 @@
<body>
<h1>What is pod ?</h1>
<p><b>pod</b> (<b>p</b>ython <b>o</b>pen <b>d</b>ocument) is a library that allows to easily generate documents whose content is dynamic. The principle is simple: you create an ODF (Open Document Format) text document (with OpenOffice Writer 2.0 or higher for example), you insert some Python code at some places inside it, and from any program written in Python, you can call pod with, as input, the OpenDocument file and a bunch of Python objects. pod generates another ODF text document (ODT) that contains the desired result. If you prefer to get the result in another format, pod can call OpenOffice in server mode to generate the result in PDF, DOC, RTF or TXT format.</p>
<p><b>pod</b> (<b>p</b>ython <b>o</b>pen <b>d</b>ocument) is a library that allows to easily generate documents whose content is dynamic. The principle is simple: you create an ODF (Open Document Format) text document (with LibreOffice Writer for example), you insert some Python code at some places inside it, and from any program written in Python, you can call pod with, as input, the OpenDocument file and a bunch of Python objects. pod generates another ODF text document (ODT) that contains the desired result. If you prefer to get the result in another format, pod can call LibreOffice in server mode to generate the result in PDF, DOC, RTF or TXT format.</p>
<h1>Getting started with pod</h1>
<p><img src="img/SimpleTest.png" align="right"/>First, create a pod template, like the one besides this text. A pod template is an ODT document where:</p>
<p>First, create a pod template, like the one below.</p>
<br/><p align="center"><img src="img/SimpleTest.png"/></p><br/>
<p>A pod template is an ODT document where:</p>
<ul>
<li>text inserted when editing the document in "track changes" mode is used for writing Python expressions;</li>
<li>Python expressions are inserted into fields;</li>
<li>notes are used for writing special Python-based statements that allow to conditionally include or repeat a portion of the document.</li>
</ul>
<p>In this template, I wrote the Python expression <span class="code">IWillTellYouWhatInAMoment</span> while being in "track changes" mode (with OpenOffice, in the Edit menu, choose Modifications->Record). I've also added 2 notes (with OpenOffice, in the Insert menu, choose Note). The first (before "It just claims...") contains the statement <span class="code">do text for i in range(3)</span>. The second contains <span class="code">do text if (not beingPaidForIt)</span>. Click <a href="podWritingTemplates.html">here</a> if you want to learn more about creating pod templates.</p>
<p><br/>In this template, I wrote the Python expression <span class="code">commercial</span> in a conditional field. With LibreOffice, click on [Ctrl]-F2 to create a field. Choose field type 'conditional text', write <span class="code">true</span> as condition, write <span class="code">commercial</span> in the <span class="code">then</span> expression and write nothing in the <span class="code">else</span> expression. Another expression ("<span class="code">i</span>") in the next line has been defined similarly. 2 notes were also added. With LibreOffice, in the Insert menu, choose Note). Click <a href="podWritingTemplates.html">here</a> if you want to learn more about creating pod templates.</p>
<p>Here is the code for calling pod for generating a result in ODT format.</p>
<p><br/>Here is the code for calling pod for generating a result in ODT format.</p>
<p class="code">
01&nbsp;&nbsp;from appy.pod.renderer import Renderer<br>
02&nbsp;&nbsp;<br>
03&nbsp;&nbsp;IWillTellYouWhatInAMoment = 'return'<br>
04&nbsp;&nbsp;beingPaidForIt = True<br>
05&nbsp;&nbsp;renderer = Renderer('SimpleTest.odt', globals(), 'result.odt')<br>
06&nbsp;&nbsp;renderer.run()<br><br>
<p class="code codePara">
01&nbsp;&nbsp;<b>from</b> appy.pod.renderer <b>import</b> Renderer<br/>
02&nbsp;&nbsp;<br/>
03&nbsp;&nbsp;commercial = 'creative'<br/>
04&nbsp;&nbsp;beingPaidForIt = True<br/>
05&nbsp;&nbsp;renderer = Renderer('SimpleTest.odt', globals(), 'result.odt')<br/>
06&nbsp;&nbsp;renderer.run()<br/>
</p>
<p>First we need to import the Renderer class. Then we define some Python variables. We must then create an instance of the Renderer (line 5), with, as parameters, the name of the pod template (we assume here that the pod template shown above is called SimpleTest.odt and lies in the current folder), a dictionary of named Python objects (here we simply take the global environment) and the name of the result file. The script will generate it, with, as content, what is shown in the image below.</p>
<p>First we need to import class <span class="code">Renderer</span>. Then we define some Python variables. We must then create an instance of the <span class="code">Renderer</span> (line 5), with, as parameters, the name of the pod template (we assume here that the pod template shown above is called SimpleTest.odt and lies in the current folder), a dictionary of named Python objects (here we simply take the global environment) and the name of the result file. The script will generate it, with, as content, what is shown in the image below.</p>
<p><img src="img/SimpleTest.res.png" align="left"/>The second line of the template is repeated 3 times. It is the effect of the <span class="code">for</span> loop in the first note. All text insertions in "track changes" mode were replaced by the results of evaluating them as Python expressions, thanks to the context given to the Renderer as second parameter of its constructor. Note that within a loop, a new name (the iterator variable, <span class="code">i</span> in this case) is added in the context and can be used within the document part that is impacted by the for loop. The last line of the template was not rendered because the condition of the second note evaluated to <span class="code">False</span>.</p>
<p align="center"><br/><img src="img/SimpleTest.res.png"/></p>
<p>Click <a href="podRenderingTemplates.html">here</a> if you want to learn more about rendering pod templates.</p>
<p><br/>The second line of the template is repeated 3 times. It is the effect of the <span class="code">for</span> loop in the first note. Content of every field was replaced by the result of evaluating it as a Python expression, thanks to the context given to the <span class="code">Renderer</span> as second parameter of its constructor. Note that within a loop, a new name (the iterator variable, <span class="code">i</span> in this case) is added in the context and can be used within the document part that is impacted by the <span class="code">for</span> loop. The last line of the template was not rendered because the condition of the second note evaluated to <span class="code">False</span>.</p>
<p><br/>Click <a href="podRenderingTemplates.html">here</a> if you want to learn more about rendering pod templates.</p>
</body>
</html>

View file

@ -9,38 +9,88 @@
<p>In order to render a pod template, the first thing to do is to create a renderer (create a <span class="code">appy.pod.Renderer</span> instance). The constructor for this class looks like this:</p>
<p class="code codePara">
<b>def</b> __init__(self, template, context, result, pythonWithUnoPath=None, ooPort=2002, stylesMapping={}, forceOoCall=False):<br>
&nbsp;&nbsp;'''This Python Open Document Renderer (PodRenderer) loads a document<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;template (p_template) which is a OpenDocument file with some elements<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;written in Python. Based on this template and some Python objects<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;defined in p_context, the renderer generates an OpenDocument file<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(p_result) that instantiates the p_template and fills it with objects<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;from the p_context. If p_result does not end with .odt, the Renderer<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;will call OpenOffice to perform a conversion. If p_forceOoCall is True,<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;even if p_result ends with .odt, OpenOffice will be called, not for<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;performing a conversion, but for updating some elements like indexes<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(table of contents, etc) and sections containing links to external<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;files (which is the case, for example, if you use the default function<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"document"). If the Python interpreter<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;which runs the current script is not UNO-enabled, this script will<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;run, in another process, a UNO-enabled Python interpreter (whose path<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;is p_pythonWithUnoPath) which will call OpenOffice. In both cases, we<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;will try to connect to OpenOffice in server mode on port p_ooPort. <br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;If you plan to make "XHTML to OpenDocument" conversions, you may specify <br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;a styles mapping in p_stylesMapping.'''
<b>class</b> Renderer:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;<b>def</b> __init__(self, template, context, result, pythonWithUnoPath=None,<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
ooPort=2002, stylesMapping={}, forceOoCall=False,<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
finalizeFunction=None, overwriteExisting=False,<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
imageResolver=None):<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''This Python Open Document Renderer (PodRenderer) loads a document<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;template (p_template) which is an ODT file with some elements<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;written in Python. Based on this template and some Python objects<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;defined in p_context, the renderer generates an ODT file<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(p_result) that instantiates the p_template and fills it with objects<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;from the p_context.<br/>
<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- If p_result does not end with .odt, the Renderer<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
will call LibreOffice to perform a conversion. If p_forceOoCall is<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
True, even if p_result ends with .odt, LibreOffice will be called, not<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
for performing a conversion, but for updating some elements like<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
indexes (table of contents, etc) and sections containing links to<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
external files (which is the case, for example, if you use the<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
default function "document").<br/>
<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- If the Python interpreter which runs the current script is not<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
UNO-enabled, this script will run, in another process, a UNO-enabled<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Python interpreter (whose path is p_pythonWithUnoPath) which will<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
call LibreOffice. In both cases, we will try to connect to LibreOffice<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
in server mode on port p_ooPort.<br/>
<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- If you plan to make "XHTML to OpenDocument" conversions, you may<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
specify a styles mapping in p_stylesMapping.<br/>
<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- If you specify a function in p_finalizeFunction, this function will<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
be called by the renderer before re-zipping the ODT result. This way,<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
you can still perform some actions on the content of the ODT file<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
before it is zipped and potentially converted. This function must<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
accept one arg: the absolute path to the temporary folder containing<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
the un-zipped content of the ODT result.<br/>
<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- If you set p_overwriteExisting to True, the renderer will overwrite<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
the result file. Else, an exception will be thrown if the result file<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
already exists.<br/>
<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- p_imageResolver allows POD to retrieve images, from "img" tags within<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
XHTML content. Indeed, POD may not be able (ie, may not have the<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
permission to) perform a HTTP GET on those images. Currently, the<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
resolver can only be a Zope application object.<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''<br/>
</p>
<p>For the <span class="code">template</span> and the <span class="code">result</span>, you can specify absolute or relative paths. I guess it is better to always specify absolute paths.</p>
<p>The <span class="code">context</span> may be either a dict, UserDict, or an instance. If it is an instance, its <span class="code">__dict__ </span>attribute is used. For example, <span class="code">context</span> may be the result of calling <span class="code">globals()</span> or <span class="code">locals()</span>. Every (key, value) pair defined in the context corresponds to a name (the key) that you can use within your template within pod statements or expressions. Those names may refer to any Python object: a function, a variable, an object, a module, etc.</p>
<p><br/>The <span class="code">context</span> may be either a dict, UserDict, or an instance. If it is an instance, its <span class="code">__dict__ </span>attribute is used. For example, <span class="code">context</span> may be the result of calling <span class="code">globals()</span> or <span class="code">locals()</span>. Every (key, value) pair defined in the context corresponds to a name (the key) that you can use within your template within pod statements or expressions. Those names may refer to any Python object: a function, a variable, an object, a module, etc.</p>
<p>Once you have the <span class="code">Renderer</span> instance, simply call its <span class="code">run</span> method. This method may raise a <span class="code">appy.pod.PodError</span> exception.</p>
<p>Since pod 0.0.2, you may put a XHTML document somewhere in the context and ask pod to convert it as a chunk of OpenDocument into the resulting OpenDocument. You may want to customize the mapping between XHTML and OpenDocument styles. This can be done through the <span class="code">stylesMapping</span> parameter. A detailed explanation about the "XHTML to OpenDocument" abilities of pod may be found <a href="podWritingAdvancedTemplates.html#xhtml">here</a>.</p>
<p><br/>Since pod 0.0.2, you may put a XHTML document somewhere in the context and ask pod to convert it as a chunk of OpenDocument into the resulting OpenDocument. You may want to customize the mapping between XHTML and OpenDocument styles. This can be done through the <span class="code">stylesMapping</span> parameter. A detailed explanation about the "XHTML to OpenDocument" abilities of pod may be found <a href="podWritingAdvancedTemplates.html#xhtml">here</a>.</p>
<h1><a name="resultFormats"></a>Result formats</h1>
<p>If <span class="code">result</span> ends with <span class="code">.odt</span>, OpenOffice will NOT be called (unless <span class="code">forceOoCall</span> is <span class="code">True</span>). pod does not need OpenOffice to generate a result in ODT format, excepted in the following cases:</p>
<p>If <span class="code">result</span> ends with <span class="code">.odt</span>, LibreOffice will NOT be called (unless <span class="code">forceOoCall</span> is <span class="code">True</span>). pod does not need LibreOffice to generate a result in ODT format, excepted in the following cases:</p>
<ul>
<li>you need to update fields in the result (ie a table of contents);</li>
@ -56,129 +106,20 @@
<li><span class="code">.txt</span>,</li>
</ul>
<p>OpenOffice will be called in order to convert a temporary ODT file rendered by pod into the desired format. This will work only if your Python interpreter knows about the Python UNO bindings. UNO is the OpenOffice API. If typing <span class="code">import uno</span> at the interpreter prompt does not produce an error, your interpreter is UNO-enabled. If not, there is probably a UNO-enabled Python interpreter within your OpenOffice copy (in &lt;OpenOfficePath&gt;/program). In this case you can specify this path in the <span class="code">pythonWithUnoPath</span> parameter of the <span class="code">Renderer</span> constructor. Note that when using a UNO-disabled interpreter, there will be one additional process fork for launching a Python-enabled interpreter.</p>
<p>LibreOffice will be called in order to convert a temporary ODT file rendered by pod into the desired format. This will work only if your Python interpreter knows about the Python UNO bindings. UNO is the OpenOffice API. If typing <span class="code">import uno</span> at the interpreter prompt does not produce an error, your interpreter is UNO-enabled. If not, there is probably a UNO-enabled Python interpreter within your LibreOffice copy (in &lt;LibreOfficePath&gt;/program). In this case you can specify this path in the <span class="code">pythonWithUnoPath</span> parameter of the <span class="code">Renderer</span> constructor. Note that when using a UNO-disabled interpreter, there will be one additional process fork for launching a Python-enabled interpreter.</p>
<p>During rendering, pod uses a temp folder located at <span class="code">&lt;result&gt;.temp</span>.</p>
<h1><a name="ooInServerMode"></a>Launching OpenOffice in server mode</h1>
<h1><a name="ooInServerMode"></a>Launching LibreOffice in server mode</h1>
<p>You launch OpenOffice in server mode by running the command:</p>
<p>You launch LibreOffice in server mode by running the command (under Linux):</p>
<p>(under Windows: ) <span class="code">call "[path_to_oo]\program\soffice" "-accept=socket,host=localhost,port=2002;urp;"&amp;</span> (put this command in .bat file, for example)</p>
<p class="code codePara">soffice -invisible -headless "-accept=socket,host=localhost,port=2002;urp;"</p>
<p>Under Windows you may also need to define this environment variable (with OpenOffice 3.x) (here it is done in Python):</p>
<p>Under Windows it may look like:</p>
<p class="code">os.environ['URE_BOOTSTRAP']='file:///C:/Program%20Files/OpenOffice.org%203/program/fundamental.ini'</p>
<p>(under Linux: ) <span class="code">soffice "-accept=socket,host=localhost,port=2002;urp;"</span></p>
<p class="code codePara">"[path_to_lo]\program\soffice" -invisible -headless "-accept=socket,host=localhost,port=2002;urp;"</p>
<p>Of course, use any port number you prefer.</p>
<p>Unfortunately, OpenOffice, even when launched in server mode, needs a Windowing system. So if your server runs Linux you will need to run a X server on it. If you want to avoid windows being shown on your server, you may launch a VNC server and define the variable DISPLAY=:1. If you run Ubuntu server and if you install package "openoffice.org-headless" you will not need a X server. On other Linux flavours you may also investigate the use of xvfb.</p>
<h1><a name="exampleDjango"></a>Rendering a pod template with Django</h1>
<p>pod can be called from any Python program. Here is an example of integration of pod with Django for producing a PDF through the web. In the first code excerpt below, in a Django view I launch a pod Renderer and I give him a mix of Python objects and functions coming from various places (the Django session, other Python modules, etc).</p>
<p class="code">
01 gotTheLock = Lock.acquire(10)<br>
02 if gotTheLock:<br>
03&nbsp;&nbsp;&nbsp;&nbsp;template = '%s/pages/resultEpmDetails.odt' % os.path.dirname(faitesletest.__file__)<br>
04&nbsp;&nbsp;&nbsp;&nbsp;params = self.getParameters()<br>
05&nbsp;&nbsp;&nbsp;&nbsp;# Add 'time' package to renderer context<br>
06&nbsp;&nbsp;&nbsp;&nbsp;import time<br>
07&nbsp;&nbsp;&nbsp;&nbsp;params['time'] = time<br>
08&nbsp;&nbsp;&nbsp;&nbsp;params['i18n'] = i21c(self.session['language'])<br>
09&nbsp;&nbsp;&nbsp;&nbsp;params['floatToString'] = Converter.floatToString<br>
10&nbsp;&nbsp;&nbsp;&nbsp;tmpFolder = os.path.join(os.path.dirname(faitesletest.__file__), 'temp')<br>
11&nbsp;&nbsp;&nbsp;&nbsp;resultFile = os.path.join(tmpFolder, '%s_%f.%s' % (self.session.session_key, time.time(), self.docFormat))<br>
12&nbsp;&nbsp;&nbsp;&nbsp;try:<br>
13&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;renderer = appy.pod.renderer.Renderer(template, params, resultFile, coOpenOfficePath)<br>
14&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;renderer.run()<br>
15&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Lock.release()<br>
16&nbsp;&nbsp;&nbsp;&nbsp;except PodError, pe:<br>
17&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Lock.release()<br>
18&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;raise pe<br>
19&nbsp;&nbsp;&nbsp;&nbsp;docFile = open(resultFile, 'rb')<br>
20&nbsp;&nbsp;&nbsp;&nbsp;self.session['doc'] = docFile.read()<br>
21&nbsp;&nbsp;&nbsp;&nbsp;self.session['docFormat'] = self.docFormat<br>
22&nbsp;&nbsp;&nbsp;&nbsp;docFile.close()<br>
23&nbsp;&nbsp;&nbsp;&nbsp;os.remove(resultFile)<br>
24 else:<br>
25&nbsp;&nbsp;&nbsp;&nbsp;raise ViaActionError('docError')<br>
</p>
<p>When I wrote this, I was told of some unstabilities of OpenOffice in server mode (! this info is probably outdated, it was written in 2007). So I decided to send to OpenOffice one request at a time through a Locking system (lines 1 and 2). My site did not need to produce a lot of PDFs, but it may not be what you need for your site.</p>
<p>Line 11, I use the Django session id and current time to produce in a temp folder a unique name for dumping the PDF on disk. Let's assume that <span class="code">self.docFormat</span> is <span class="code">pdf</span>. Then, line 20, I read the content of this file into the Django session. I can then remove the temporary file (line 23). Finally, the chunk of code below is executed and a <span class="code">HttpResponse</span> instance is returned.</p>
<p class="code">
01 from django.http import HttpResponse<br>
02 ...<br>
03&nbsp;&nbsp;&nbsp;&nbsp;# I am within a Django view<br>
04&nbsp;&nbsp;&nbsp;&nbsp;res = HttpResponse(mimetype=self.getDocMimeType()) # Returns "application/pdf" for a PDF and "application/vnd.oasis.opendocument.text" for an ODT<br>
05&nbsp;&nbsp;&nbsp;&nbsp;res['Content-Disposition'] = 'attachment; filename=%s.%s' % (<br>
06&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.getDocName(), self.extensions[self.getDocMimeType()])<br>
07&nbsp;&nbsp;&nbsp;&nbsp;res.write(self.getDocContent())<br>
08&nbsp;&nbsp;&nbsp;&nbsp;# Clean session<br>
09&nbsp;&nbsp;&nbsp;&nbsp;self.session['doc'] = None<br>
10&nbsp;&nbsp;&nbsp;&nbsp;self.session['docFormat'] = None<br>
11&nbsp;&nbsp;&nbsp;&nbsp;return res<br>
</p>
<p>Line 7, <span class="code">self.getDocContent()</span> returns the content of the PDF file, that we stored in <span class="code">self.session['doc']</span> (line 20 of previous code chunk). Line 5, by specifying <span class="code">attachment</span> we force the browser to show a popup that asks the user if he wants to store the file on disk or open it with a given program. You can also specify <span class="code">inline</span>. In this case the browser will try to open the file within its own window (potentially with the help of a plugin).</p>
<h1><a name="examplePlone"></a>Rendering a pod template with Plone</h1>
<p>The following code excerpt shows how to render the "ODT view" of a Plone object. Here, we start from an Archetypes class that was generated with ArchGenXML. It is a class named <span class="code">Avis</span> that is part of a Zope/Plone product named <span class="code">Products.Avis</span>. While "Avis" content type instances can be created, deleted, edited, etc, as any Plone object, it can also be rendered as ODT (and in any other format supported by pod/OpenOffice). As shown below we have added a method <span class="code">generateOdt</span> to the Avis class, that calls a private method that does the job.</p>
<p class="code">
01 import appy.pod.renderer<br>
02 import Products.Avis<br>
03 ...<br>
04&nbsp;&nbsp;&nbsp;&nbsp;# We are in the Avis class<br>
05&nbsp;&nbsp;&nbsp;&nbsp;security.declarePublic('generateOdt')<br>
06&nbsp;&nbsp;&nbsp;&nbsp;def generateOdt(self, RESPONSE):<br>
07&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''Generates the ODT version of this advice.'''<br>
08&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return self._generate(RESPONSE, 'odt')<br>
09<br>
10&nbsp;&nbsp;&nbsp;&nbsp;# Manually created methods<br>
11<br>
12&nbsp;&nbsp;&nbsp;&nbsp;def _generate(self, response, fileType):<br>
13&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'''Generates a document that represents this advice.<br>
14&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;The document format is specified by p_fileType.'''<br>
15&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# First, generate the ODT in a temp file<br>
16&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tempFileName = '/tmp/%s.%f.%s' % (self._at_uid, time.time(), fileType)<br>
17&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;renderer = appy.pod.renderer.Renderer('%s/Avis.odt' % os.path.dirname(Products.Avis.__file__), {'avis': self}, tempFileName)<br>
18&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;renderer.run()<br>
19&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Tell the browser that the resulting page contains ODT<br>
20&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;response.setHeader('Content-type', 'application/%s' % fileType)<br>
21&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;response.setHeader('Content-disposition', 'inline;filename="%s.%s"' % (self.id, fileType))<br>
22&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Returns the doc and removes the temp file<br>
23&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;f = open(tempFileName, 'rb')<br>
24&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;doc = f.read()<br>
25&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;f.close()<br>
26&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;os.remove(tempFileName)<br>
27&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return doc<br>
</p>
<p>First, I plan to create the ODT file on disk, in a temp folder. Line 16, I use a combination of the attribute <span class="code">self._at_uid</span> of the Zope/Plone object and the current time to produce a unique name for the temp file. Then (lines 17 and 18) I create and call a pod renderer. I give him a pod template which is located in the Products folder (Avis subfolder) of my Zope instance. Lines 20 and 21, I manipulate the Zope object that represents the HTTP <span class="code">response</span> to tell Zope that the resulting page will be an ODT document. Finally, the method returns the content of the temp file (line 27) that is deleted (line 26). In order to trigger the ODT generation from the "view" page of the Avis content type, I have overridden the <span class="code">header</span> macro in Products/Avis/skins/Avis/avis_view.pt:</p>
<p class="code">
&lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US" i18n:domain="plone"&gt;<br>
&lt;body&gt;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&lt;div metal:define-macro="header"&gt;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&lt;div class="documentActions"&gt;&lt;a href="" tal:attributes="href python:here['id'] + '/generateOdt'"&gt;Generate ODT version&lt;/a&gt;&lt;/div&gt;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&lt;h1 tal:content="here/title"&gt;Advice title&lt;/h1&gt;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&lt;div class="discreet"&gt;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;If you want to edit this advice, click on the "edit" tab above.<br>
&nbsp;&nbsp;&nbsp;&nbsp;&lt;/div&gt;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&lt;p&gt;&lt;/p&gt;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&lt;/div&gt;<br>
&lt;/body&gt;<br>
&lt;/html&gt;<br>
</p>
<p>Hum... I know there are cleanest ways to achieve this result :-D</p>
</body>
</html>

View file

@ -4,6 +4,9 @@
<link rel="stylesheet" href="appy.css" type="text/css">
</head>
<body>
<div class="focus">Historically, defining Python expressions with pod was done via text inserted in 'track changes' mode (with LibreOffice, in the Edit menu, enable/disable this mode by clicking on Modifications->Record). This page still shows examples with track-changed text, although conditional fields (as explained <a href="pod.html">here</a>) have become the preferred way to insert Python expressions.</div>
<h1><a name="fromClause"></a>Inserting arbitrary content: the <span class="code">from</span> clause</h1>
<p>In the section <a href="podWritingTemplates.html">"Writing templates"</a>, you've learned how to write pod statements (<span class="code">if</span>, <span class="code">for</span>). Every pod statement is linked to a given part of the pod template (a text paragraph, a title, a table, a table row, etc) and conditions how this part is rendered in the result (the <span class="code">if</span> statement, for example, renders the part only if the related condition is <span class="code">True</span>). This way to work has its limits. Indeed, you can't insert what you want into the result: you are forced to use the part of the document that is the target of the statement. Of course, in this part, you can still write funny things like Python expressions and statements, but it may not be sufficient.</p>
@ -12,19 +15,19 @@
<p>In the example below, the statement has a <span class="code">from</span> clause that produces a simple paragraph containing 'Hello'.</p>
<p><img src="img/SimpleFrom.png"/></p>
<p align="center" class="by"><img src="img/SimpleFrom.png"/></p>
<p>In the result, the targeted paragraph has been replaced by the chunk of odt content specified in the <span class="code">from</span> expression. Note that the <span class="code">from</span> clause MUST start on a new line in the note. Else, it will be considered as part of statement and will probably produce an error.</p>
<p><img src="img/SimpleFrom.res.png"/></p>
<p align="center" class="by"><img src="img/SimpleFrom.res.png"/></p>
<p>Surprise! This statement is neither a 'if' not a 'for' statement... It is a "null" statement whose sole objective is to replace the target by the content of the <span class="code">from</span> expression. But you can also add <span class="code">from</span> clauses to 'if' and 'for' statements. Here is an example with a 'for' statement.</p>
<p><img src="img/FromWithFor.png"/></p>
<p align="center" class="by"><img src="img/FromWithFor.png"/></p>
<p>Here's the result. Note that within the <span class="code">from</span> clause you may use the iterator variable (<span class="code">i</span>, in this case) defined by the <span class="code">for</span> statement.</p>
<p><img src="img/FromWithFor.res.png"/></p>
<p align="center" class="by"><img src="img/FromWithFor.res.png"/></p>
<p>Actually, when you don't specify a <span class="code">from</span> clause in a statement, pod generates an implicit <span class="code">from</span> clause whose result comes from the odt chunk that is the target of the statement.</p>
@ -37,7 +40,7 @@
<p>One of these functions is the <span class="code">xhtml</span> function, that allows to convert chunks of XHTML documents (given as strings in the context) into chunks of OpenDocument within the resulting OpenDocument. This functionality is useful, for example, when using pod with systems like Plone, that maintain a part of their data in XHTML format (Kupu fields, for example).</p>
<p>Suppose you want to render this chunk of XHTML code at some place in your pod result:</p>
<table align="center">
<br/><table align="center" class="list">
<tr>
<th>XHTML code</th>
<th>XHTML rendering (Plone)</th>
@ -67,10 +70,11 @@
</td>
</tr>
</table>
<br/>
<p>pod comes with a function named <span class="code">xhtml</span> that you may use within your pod templates, like this:</p>
<p align="center"><img src="img/xhtmlTemplate.png"/></p>
<p align="center" class="by"><img src="img/xhtmlTemplate.png"/></p>
<p>In this example, the name <span class="code">dummy</span> is available in the context, and <span class="code">dummy.getAt1()</span> produces a Python string that contains the XHTML chunk shown above. This string is given as paremeter of the built-in pod <span class="code">xhtml</span> function.</p>
@ -78,7 +82,7 @@
<p>The rendering produces this document:</p>
<p align="center"><img src="img/xhtmlResult.png"/></p>
<p align="center" class="by"><img src="img/xhtmlResult.png"/></p>
<p>The OpenDocument rendering is a bit different than the XHTML rendering shown above. This is because pod uses the styles found in the pod template and tries to make a correspondence between style information in the XHTML chunk and styles present in the pod template. By default, when pod encounters a XHTML element:</p>
<ul>
@ -101,7 +105,7 @@
<p>Second, each time you invoke the <span class="code">xhtml</span> function in a pod template, you may specify a local styles mapping in the parameter named <span class="code">stylesMapping</span>, like shown below.</p>
<p align="center"><img src="img/xhtmlStylesMapping.png"/></p>
<p align="center" class="by"><img src="img/xhtmlStylesMapping.png"/></p>
<p>Local styles mappings override what you have (potentially) defined in the global styles mapping.</p>
@ -116,7 +120,7 @@
<p>This can be problematic if, for instance, you want to use special style-related attributes, specially for <span class="code">li</span> elements that correspond to paragraphs. This is why any pod template includes some predefined styles that may apply to these elements. The following table shows them, grouped by element type.</p>
<table>
<br/><table class="list" align="center">
<tr>
<th>Element</th>
<th>Available style(s)</th>
@ -143,6 +147,7 @@
<td>No pod-specific style is proposed at present. The unique default pod style of this element will be used.</td>
</tr>
</table>
<br/>
<p>In order to use one of those styles, you can specify its name in the "class" attribute of the target element, or you can go through a global or local styles mapping. For example, if you need a <span class="code">li</span> element that will always stay on the same page as the paragraph below him, you can write <span class="code">&lt;li class="podItemKeepWithNext"&gt;&lt;/li&gt;</span>.</p>
@ -156,7 +161,7 @@
<p class="code">document(content=None, at=None, format=None, anchor='as-char')</p>
<table>
<br/><table class="list" align="center">
<tr>
<th>Parameter</th>
<th>Description</th>
@ -228,18 +233,19 @@
</td>
</tr>
</table>
<br/>
<p>The following example shows a POD template part that integrates a PNG image from disk.</p>
<p align="center"><img src="img/documentFunction1.png"/></p>
<p align="center" class="by"><img src="img/documentFunction1.png"/></p>
<p>(Note that the <span class="code">from</span> clause must be on a single line.). This could be rendered this way for example:</p>
<p align="center"><img src="img/documentFunction2.png"/></p>
<p align="center" class="by"><img src="img/documentFunction2.png"/></p>
<p>The following ODT template part reads PDF and ODT files from a database (The ZODB; it is a Plone site) and integrates them in the pod result.</p>
<p align="center"><img src="img/documentFunction3.png"/></p>
<p align="center" class="by"><img src="img/documentFunction3.png"/></p>
<p>For those who know Plone, <span class="code">annex</span> is an instance of <span class="code">ATFile</span>: <span class="code">annex.data</span> returns its binary content, while <span class="code">annex.getContentType()</span> returns is MIME type.</p>
@ -255,11 +261,11 @@
<p>Pod built-in functions are designed to be used within pod statements (<span class="code">from</span> clauses). If you try to use them in pod expressions, you will get strange results. The example below uses the <span class="code">xhtml</span> function in a pod expression.</p>
<p align="center"><img src="img/builtinFunctionInPodExpression.png"/></p>
<p align="center" class="by"><img src="img/builtinFunctionInPodExpression.png"/></p>
<p>If <span class="code">dummy.getAt1()</span> produces the XHTML chunk <span class="code">&lt;p&gt;Test1&lt;br/&gt;&lt;/p&gt;</span>, the result will look like this:</p>
<p align="center"><img src="img/builtinFunctionInPodExpression.res.png"/></p>
<p align="center" class="by"><img src="img/builtinFunctionInPodExpression.res.png"/></p>
</body>
</html>

View file

@ -4,15 +4,18 @@
<link rel="stylesheet" href="appy.css" type="text/css">
</head>
<body>
<div class="focus">Historically, defining Python expressions with pod was done via text inserted in 'track changes' mode (with LibreOffice, in the Edit menu, enable/disable this mode by clicking on Modifications->Record). This page still shows examples with track-changed text, although conditional fields (as explained <a href="pod.html">here</a>) have become the preferred way to insert Python expressions.</div>
<h1><a name="expressions"></a>Expressions</h1>
<P>Within a pod template, any text written in "track changes" mode is interpreted as a Python expression. The interpretation context is given to pod like explained <a href="podRenderingTemplates.html">here</a>. The example below contains some simple Python expressions (Python variables).</P>
<p>Within a pod template, any text written in "track changes" mode is interpreted as a Python expression. The interpretation context is given to pod like explained <a href="podRenderingTemplates.html">here</a>. The example below contains some simple Python expressions (Python variables).</p>
<p align="center"><img src="img/OnlyExpressions.png"/></p>
<p align="center" class="by"><img src="img/OnlyExpressions.png"/></p>
<p>Running appy.pod with a context that associates a value to <span class="code">expr1</span>, <span class="code">i1</span> and <span class="code">f1</span> produces a result like the one shown below.</p>
<p align="center"><img src="img/OnlyExpressions.res.png" align="center"/></p>
<p align="center" class="by"><img src="img/OnlyExpressions.res.png" align="center"/></p>
<p>Note that any style (bold, italic, etc) that is used within track-changed text is kept, but style variations within the text is ignored. For example, the first "expr1" text has an italicized "p", but this style information is ignored. The second "expr1" is bold, so the result is bold, too, and again the having an italicized "p" has no effect.</p>
@ -20,7 +23,7 @@
<p>An "if" statement is written in an ODT note and has the form <span class="code">do &lt;document part&gt; if &lt;python_expression&gt;</span>. The <span class="code">&lt;document part&gt;</span> which is the target of the statement will only be included in the result if the <span class="code">python expression</span> resolves to True (the boolean value, or any equivalent Python value, like a non empty list, string, etc). "Document parts" that can be referenced in "if" statements are presented in the table below.<p>
<table align="center">
<br/><table class="list" align="center">
<tr bgcolor="#F2F2F2">
<td width="150px"><b>Name</b></td>
<td width="150px"><b>Description</b></td>
@ -50,43 +53,44 @@
<td>A single cell within a table row</td>
</tr>
</table>
<br/>
<p>A "for" statement is also written in a ODT note and has the form <span class="code">do &lt;document part&gt; for &lt;variable_name&gt; in &lt;python_expression&gt;</span>. The <span class="code">&lt;document part&gt;</span> which is the target of the statement will be included as many times as there are items in the sequence that is defined by the <span class="code">&lt;python_expression&gt;</span>. Within the document part in question, one can use <span class="code">&lt;variable_name&gt;</span>, or any Python expression that uses it, within track-changed text (it adds this name in the context). Allowed <span class="code">&lt;document part&gt;</span>s are those described in the previous table. If <span class="code">&lt;variable_name&gt;</span> was already defined in the context, it is hidden by this new variable definition only within the scope of the targeted <span class="code">&lt;document part&gt;</span>.</p>
<p>The example below contains several "if" and "for" statements. Small yellow rectangles are ODT notes that you insert, for example, through the "Insert-&gt;Note" menu in OpenOffice. Yellow blocks containing Python code represent the content of the note.</p>
<p align="center"><img src="img/IfAndFors1.png" align="center"/></p>
<p align="center" class="by"><img src="img/IfAndFors1.png" align="center"/></p>
<p>Applying this template with a given context may produce a result like the following.</p>
<p align="center"><img src="img/IfAndFors1.res.png" align="center"/></p>
<p align="center" class="by"><img src="img/IfAndFors1.res.png" align="center"/></p>
<p>The template contained 1 section. The result contains 3 sections, because 3 groups were defined in the <span class="code">groups</span> variable (which was a Python list or tuple containing instances of some <span class="code">Group</span> class defined somewhere in a python file) used in the 1st "for" statement. The 2 first groups contained persons (instances of some <span class="code">Person</span> class), so the table was rendered in the 2 first sections (this is the effect of the second note). Those tables contain one row by person, thanks to the "for" statement in the third note. The last paragraph of the template was only rendered once, for the last group, because this group contained no person (this is the effect of the last note).</p>
<p>In this example, for each group, I wanted to include a table if persons were defined, or a paragraph <i>else</i>. In order to express this, I've defined 2 "if" statements with 2 conditions, one being the negation of the other. pod (starting from version 0.2) allows to use "else" statements: instead of writing <span class="code">do text if not group.persons</span> in the last note, I could have written <span class="code">do text else</span> instead. But do you believe I have time to rework those old screenshots? Moreover, you have to be careful with "else" statements. pod statements are independent of each other: an "if" statement and an "else" statement are really 2 different statements, unlike what happens in programming languages for example. It means that pod must be able to link "if" and "else" statements, and in some cases it is not possible. The following example contains several "else" statements that pod could unambiguously link to their corresponding "if" statements.</p>
<p align="center"><img src="img/ElseStatements.png" align="center"/></p>
<p align="center" class="by"><img src="img/ElseStatements.png" align="center"/></p>
<p>When pod encounters an "else" statement, it tries to link it to the last defined "if" statement in the part of the pod template that precedes the "else" statement. In the last example, when encountering the "else" statement numbered "4", pod links it to the "if" statement numbered "3". Once the link is done, the linked "if" statement is "consumed": it can't be linked to any other "else" statement. When pod encounters the "else" statement numbered "5", it links it to the last "unconsumed if" statement: the "if" statement numbered "2". In the same way, the "else" numbered "6" is linked to the "if" numbered "1". Here is the result:
</p>
<p align="center"><img src="img/ElseStatements.res.png" align="center"/></p>
<p align="center" class="by"><img src="img/ElseStatements.res.png" align="center"/></p>
<p>In the example below, however, pod does not produce the desired result.</p>
<p align="center"><img src="img/ElseAmbiguous.png" align="center"/></p>
<p align="center" class="by"><img src="img/ElseAmbiguous.png" align="center"/></p>
<p>Here I wanted to link the "else" to the first "if", but pod linked it to the second one instead, mistakenly rendering the last line:</p>
<p align="center"><img src="img/ElseAmbiguous.res.png" align="center"/></p>
<p align="center" class="by"><img src="img/ElseAmbiguous.res.png" align="center"/></p>
<p>With the currently presented concepts, here you would be forced to replace the "else" by an "if" that duplicates and negates the condition written in the first "if". This duplication could lead to maintenance problems (ie you update the condition in the first "if" and forget to update its negation in the third one). So if we want to conform to the <i>null-IT</i> principle and make pod a tool as invisible as possible, we need here the concept of "named statement". From pod 0.2, any statement may be named. The name must conform to the regular expression "\w+" (it can contain alphanumeric characters only + the underscore as explained <a href="http://docs.python.org/lib/re-syntax.html" target="_blank">here</a>) and must be unique among the (not consumed) named statements in the pod template. The next example is the right way to produce the result we wanted to achieve in the last example: the first "if" is named and can thus be referred to explicitly by the "else".</p>
<p align="center"><img src="img/ElseNotAmbiguous.png" align="center"/></p>
<p align="center" class="by"><img src="img/ElseNotAmbiguous.png" align="center"/></p>
<p>The result is superb! pod is invisible!</p>
<p align="center"><img src="img/ElseNotAmbiguous.res.png" align="center"/></p>
<p align="center" class="by"><img src="img/ElseNotAmbiguous.res.png" align="center"/></p>
<p>Note that it is legal to name any pod statement; however, pod uses statement names only for connecting "if" and "else" statements (at least for now).</p>
@ -98,11 +102,11 @@
<p>Besides the "if" and "else" statements, pod also proposes an "if" expression. Indeed, if the things you want to conditionnally include are small enough (a few words for example) you may find more convenient to use the "if" expression, that is implemented as a function named "test" that is directly available in the pod context as a default function. Here is an example:</p>
<p align="center"><img src="img/IfExpression.png" align="center"/></p>
<p align="center" class="by"><img src="img/IfExpression.png" align="center"/></p>
<p>The result:</p>
<p align="center"><img src="img/IfExpression.res.png" align="center"/></p>
<p align="center" class="by"><img src="img/IfExpression.res.png" align="center"/></p>
<h1><a name="errors"></a>Errors management</h1>
@ -110,53 +114,53 @@
<p>The example below defines an expression that uses a variable "A" that is not defined.</p>
<p align="center"><img src="img/ErrorExpression.png" align="center"/></p>
<p align="center" class="by"><img src="img/ErrorExpression.png" align="center"/></p>
<p>Instead of containing the result of evaluating <span class="code">A+B</span>, the result contains a note that gives an explanation about the error and the Python traceback, as shown below.</p>
<p align="center"><img src="img/ErrorExpression.res.png" align="center"/></p>
<p align="center" class="by"><img src="img/ErrorExpression.res.png" align="center"/></p>
<p>In the next example, we try to render a table if a condition is True, but evaluating the condition produces an error.</p>
<p align="center"><img src="img/ErrorIf.png" align="center"/></p>
<p align="center" class="by"><img src="img/ErrorIf.png" align="center"/></p>
<p>The table is rendered but only contains a note explaining what happened.</p>
<p align="center"><img src="img/ErrorIf.res.png" align="center"/></p>
<p align="center" class="by"><img src="img/ErrorIf.res.png" align="center"/></p>
<p>The following example illustrate errors produced while evaluating "for" statements.</p>
<p>The following example illustrates errors produced while evaluating "for" statements.</p>
<p align="center"><img src="img/ErrorForRuntime.png" align="center"/></p>
<p align="center" class="by"><img src="img/ErrorForRuntime.png" align="center"/></p>
<p>Notice that in the result shown below, the second note does not contain a traceback. Indeed, Python did not produce an error while evaluating expression <span class="code">45</span>. pod simply expected a sequence.</p>
<p align="center"><img src="img/ErrorForRuntime.res.png" align="center"/></p>
<p align="center" class="by"><img src="img/ErrorForRuntime.res.png" align="center"/></p>
<p>The previous examples were all examples of "runtime" errors, ie errors that were produced while rendering the template. Errors may also occur at "parsing" time, when pod reads the content of notes and track-changed text to analyse the Python-like expressions. In the example below, statements do not respect the pod syntax.</p>
<p align="center"><img src="img/ErrorForParsetime.png" align="center"/></p>
<p align="center" class="by"><img src="img/ErrorForParsetime.png" align="center"/></p>
<p>Oh! So much effort has been made to produce clear error messages that I do not need to add a comment here :-)</p>
<p align="center"><img src="img/ErrorForParsetime.res.png" align="center"/></p>
<p align="center" class="by"><img src="img/ErrorForParsetime.res.png" align="center"/></p>
<h1><a name="tableCells"></a>Repetition or conditional inclusion of table cells is a bit more tricky</h1>
<p>Playing with table cells may render tables that do not contain a number of cells that produce complete rows. In the example below, the "for" statement will produce 5 cells in a 3-columns table (there are 3 persons defined in <span class="code">persons</span>). So there is one missing cell. If you are bored with my explanations, you can make a pause now and listen to some <a href="http://www.archive.org/download/NullIdControleK/06MissingCells.mp3">musical illustration</a> of this "missing cell" problem (ok I agree the artist has its own point of view on this question).</p>
<p align="center"><img src="img/ForCellNotEnough.png" align="center"/></p>
<p align="center" class="by"><img src="img/ForCellNotEnough.png" align="center"/></p>
<p>Hurrah! pod added the missing cell for you.</p>
<p align="center"><img src="img/ForCellNotEnough.res.png" align="center"/></p>
<p align="center" class="by"><img src="img/ForCellNotEnough.res.png" align="center"/></p>
<p>Sometimes the number of cells it too high. In the example below, again 3 persons will be inserted, producing a row of 6 cells instead of 4.</p>
<p align="center"><img src="img/ForCellTooMuch2.png" align="center"/></p>
<p align="center" class="by"><img src="img/ForCellTooMuch2.png" align="center"/></p>
<p>Hurrah again. pod broke this row into 2 complete rows.</p>
<p align="center"><img src="img/ForCellTooMuch2.res.png" align="center"/></p>
<p align="center" class="by"><img src="img/ForCellTooMuch2.res.png" align="center"/></p>
<p>Who said it is exactly the same problem as the previous one? Hmm. Hmm. Indeed. Ok. I must recognize that "too much" or "not enough" cells is just a question of point of view. You're right. But I will not change the names of my test files or refactor them to eliminate redundant ones. You are aware of this strange situation, now let's continue.</p>
@ -168,42 +172,28 @@
<p>Very often, when creating complex pod templates, you will create sections containing tables containing tables of tables of tables etc. Those hierarchical levels exist just because you need to insert a statement, but you don't care about keeping them in the final result. You can tell pod to remove tables and sections from the final result by using the "minus" operator, like shown below.</p>
<p align="center"><img src="img/ForTableMinus.png" align="center"/></p>
<p align="center" class="by"><img src="img/ForTableMinus.png" align="center"/></p>
<p>The result, produced while <span class="code">persons</span> is a list of 8 persons, is shown below.</p>
<p align="center"><img src="img/ForTableMinus.res.png" align="center"/></p>
<p align="center" class="by"><img src="img/ForTableMinus.res.png" align="center"/></p>
<p>Of course, do that only if you don't care about any formatting associated with the table or section to remove. In the previous example, using the "minus" operator was not the right choice if we wanted to print those addresses on envelopes. In other cases, using this operator may prevent occurrence of a bug in OpenOffice (2.1): a table that spans multiple pages may be truncated after 1 or 2 pages. Some manual edition (like inserting a "carriage return" somewhere) is needed to correct this problem; so if you planned to convert your result automatically to PDF for example you are KO.</p>
<p>The "minus" operator is only allowed for sections and one-cell tables. When applying it to a table that has more than one cell...</p>
<p align="center"><img src="img/ForTableMinusError.png" align="center"/></p>
<p align="center" class="by"><img src="img/ForTableMinusError.png" align="center"/></p>
<p>... pod complains.</p>
<p align="center"><img src="img/ForTableMinusError.res.png" align="center"/></p>
<p align="center" class="by"><img src="img/ForTableMinusError.res.png" align="center"/></p>
<h1><a name="tips"></a>Tips for writing pod templates with OpenOffice.org</h1>
<p>When you write text in "track changes" mode (for writing pod expressions), sometimes OpenOffice (2.2) splits this text in several chunks. For example, while typing "azerty" OpenOffice may create 2 contiguous text inserts, "aze" and "rty". So the best way to write expressions in a pod template with OpenOffice is to perform the following steps:</p>
<ul>
<li>write and edit your expression in normal mode;</li>
<li>when you're happy with it, cut it;</li>
<li>switch in "track changes" mode (Edit -> Changes -> Record)</li>
<li>paste your expression</li>
<li>switch back in normal mode (clicking again on Edit -> Changes -> Record will uncheck this menu option.)</li>
</ul>
<p>Of course this can be boring. So you won't do that systematically. In order to check that your expressions are not split, go to Edit -> Changes -> Accept or reject. Do not accept or reject anything, but use the arrows to navigate within your expressions. You will immediately detect a truncated expression (or potentally merged expressions)</p>
<h1><a name="tips2"></a>Try to minimize Python code within pod templates</h1>
<h1><a name="tips"></a>Try to minimize Python code within pod templates</h1>
<p>pod templates should be as readable as possible. One day (who knows?), a non-developer may need to consult one of them. So try minimize the amount of Python code you put in it. For example, instead of writing a complex expression or a complex condition within a statement, think about writing this code in a Python method with a nice name and simply call it within your template (of course the object on which this method is defined must be in the pod context).</p>
<p>If you need to use the result of a resources-consuming method in several places within a pod template, instead of calling this method in several expressions (pod will execute the method in each expression), call it once before rendering the template and give the result under a new name in the context passed to pod.</p>
<p>For more information about calling pod to render a template with a given context, come <a href="podRenderingTemplates.html">here !</a></p>
<p>For more information about calling pod to render a template with a given context, check <a href="podRenderingTemplates.html">here.</a></p>
</body>
</html>

View file

@ -226,7 +226,8 @@ class Tool(ModelClass):
def isManager(self): pass
def isManagerEdit(self): pass
lf = {'layouts':'f'}
title = gen.String(show=False, page=gen.Page('main', show=False), **lf)
title = gen.String(show=False, page=gen.Page('main', show=False),
default='Configuration', **lf)
mailHost = gen.String(default='localhost:25', **lf)
mailEnabled = gen.Boolean(default=False, **lf)
mailFrom = gen.String(default='info@appyframework.org', **lf)

View file

@ -44,7 +44,7 @@ img { border: 0; vertical-align: middle}
/* Styles that apply when viewing content of XHTML fields, that mimic styles
that ckeditor uses for displaying XHTML content in the edit view. */
.xhtml { margin-top: 10px }
.xhtml { margin-top: 5px }
.xhtml img { margin-right: 5px }
.xhtml p { margin: 3px 0 7px 0}

View file

@ -104,8 +104,8 @@ class Renderer:
from the p_context.
- If p_result does not end with .odt, the Renderer
will call OpenOffice to perform a conversion. If p_forceOoCall is
True, even if p_result ends with .odt, OpenOffice will be called, not
will call LibreOffice to perform a conversion. If p_forceOoCall is
True, even if p_result ends with .odt, LibreOffice will be called, not
for performing a conversion, but for updating some elements like
indexes (table of contents, etc) and sections containing links to
external files (which is the case, for example, if you use the
@ -114,8 +114,8 @@ class Renderer:
- If the Python interpreter which runs the current script is not
UNO-enabled, this script will run, in another process, a UNO-enabled
Python interpreter (whose path is p_pythonWithUnoPath) which will
call OpenOffice. In both cases, we will try to connect to OpenOffice
in server mode on port p_ooPort.
call LibreOffice. In both cases, we will try to connect to
LibreOffice in server mode on port p_ooPort.
- If you plan to make "XHTML to OpenDocument" conversions, you may
specify a styles mapping in p_stylesMapping.