Sophie

Sophie

distrib > Mandriva > 2010.0 > i586 > media > contrib-release > by-pkgid > 8b32d38f0094104a5fd847636983daa8 > files > 126

zope-doc-2.11.2-11mdv2010.0.i586.rpm

<html>
<head>
<title>Chapter 10: Advanced Zope Scripting</title>
</head>
<body bgcolor="#FFFFFF">
<h1>Chapter 10: Advanced Zope Scripting</h1>
<p>  Zope manages your presentation, logic and data with objects.  So far,
  you've seen how Zope can manage presentation with DTML, and data with
  files and images.  This chapter shows you how to add <em>Script</em> objects
  that allows you to write scripts in <a href="http://www.python.org">Python</a>, and
  <a href="http://www.perl.org">Perl</a> through your web browser.</p><p>  What is <em>logic</em> and how does it differ from presentation? Logic provides
  the actions that change objects, send messages, test conditions and
  respond to events, whereas presentation formats and displays information
  and reports. Typically you will use DTML to handle presentation, and Zope
  scripting with Python and Perl to handle logic.</p><h2>  Zope Scripts</h2>
<p>    Zope <em>Script</em> objects are objects that encapsulate a small chunk
    of code written in a programming language.  Currently, Zope
    provides <em>Python-based Scripts</em>, which are written in the Python
    language, and <em>Perl-based Scripts</em> which are written in the Perl
    language.  Script objects are new as of Zope 2.3, and are the
    preferred way to write programming logic in Zope.</p><p>    So far in this book you have heavily used DTML Methods and
    Documents to create simple web applications in Zope. DTML allows
    you to perform simple scripting operations such as string
    manipulation.  For the most part, however, DTML Methods should be
    used for presentation.  DTML Methods are explained in Chapters 4,
    "Dynamic Content with DTML", and Chapter 8, "Variables and
    Advanced DTML".</p><p>    Here is an overview of Zope's
    scripts:<dl>
<dt>      Python-based Scripts</dt>
<dd>You can use Python, a general purpose
      scripting language, to control Zope objects and perform other
      tasks. These Scripts give you general purpose programming
      facilities within Zope.</dd>
<dt>      Perl-based Scripts</dt>
<dd>You can use Perl, a powerful text processing
      language, to script Zope objects and access Perl
      libraries. These scripts offer benefits similar to those of
      Python-based Scripts, but may be more appealing for folks who
      know Perl but not Python, or who want to use Perl libraries for
      which there are no Python equivalents.</dd>
</dl>
</p><p>    You can add these scripts to your Zope application just like any
    other object.</p><h2>  Calling Scripts</h2>
<p>    Zope scripts are called from the web or from other scripts or
    objects. Almost any type of script can be called by any other type
    of object; you can call a Python-based Script from a DTML Method,
    or a built-in method from a Perl-based Script. In fact scripts can
    call scripts which call other scripts, and so on. As you saw in
    Chapter 4, "Dynamic Content with DTML", you can replace a script
    with a script implemented in another language transparently. For
    example if you're using Perl to perform a task, but later decide
    that it would be better done in Python, you can usually replace
    the script with a Python-based Script with the same id.</p><p>    When you call a script, the way that you call it gives the script
    a context in which to execute.  A script's context is
    important. For example, when you call a script you usually want to
    single out some object that is central to the script's task.  You
    would call the script in the context of the object on which you
    want it to carry out its task.  It is simpler to just say that you
    are calling the script <em>on</em> the object.</p><h3>    Calling Scripts From the Web</h3>
<p>      You can call a script directly from with web by visiting its
      URL. You can call a single script on different objects by using
      different URLS.  This works because by using different URLs you
      can give your scripts different contexts, and scripts can
      operate differently depending on their context. This is a
      powerful feature that enables you to apply logic to objects like
      documents or folders without having to embed the actual code
      within the object.</p><p>      To call a script on an object from the web, simply visit the URL
      of the object, followed by the name of the script. This places the
      script in the context of your object. For example suppose you have
      a collection of objects and scripts as shown in <a href="#8-1">Figure 8-1</a>.</p><p>      <a name="8-1"></a>
<img src="Figures/8-1.png" alt="A collection of objects and scripts">
<p><b>Figure 8-1</b> A collection of objects and scripts</p>
 </p><p>      To call the <em>feed</em> script on the <em>hippo</em> object you would visit the URL
      <em>Zoo/LargeAnimals/hippo/feed</em> To call the <em>feed</em> script on the
      <em>kangarooMouse</em> object you can visit the URL
      <em>Zoo/SmallAnimals/kangarooMouse/feed</em>. These URLs place the
      <em>feed</em> script in the context of the <em>hippo</em> and <em>kargarooMouse</em>
      objects, respectively.</p><p>      Zope uses a URL as a map to find what object and what script you
      want to call. </p><p>      Zope breaks apart the URL and compares it to the object
      hierarchy, working backwards until it finds a match for each
      part.  This process is called <em>URL traversal</em>. For example, when
      you give Zope the URL <em>Zoo/LargeAnimals/hippo/feed</em>,  it starts
      at the root folder and looks for an object named <em>Zoo</em>. It then
      moves to the <em>Zoo</em> folder and looks for an object named
      <em>LargeAnimals</em>. It moves to the <em>LargeAnimals</em> folder and looks
      for an object named <em>hippo</em>. It moves to the <em>hippo</em> object and
      looks for an object named <em>feed</em>. The <em>feed</em> script can't be
      found in the <em>hippo</em> object and is located in the <em>Zoo</em> folder
      by a process called <em>acquisition</em>.</p><p>      Acquisition does two things. First it tries to find the object
      in the current object's containers. If that doesn't work it
      backs up along the URL path and tries again.  In this example
      Zope first looks for the <em>feed</em> object in <em>hippo</em>, then it goes
      to the first container, <em>LargeAnimals</em>, and then to the next
      container, <em>Zoo</em>, where <em>feed</em> is finally found.</p><p>      Now Zope has reached the end of the URL. It calls the last
      object found, <em>feed</em>. The <em>feed</em> script operates on its context
      which is the second to last object found, the <em>hippo</em>
      object. This is how the <em>feed</em> script is called on the <em>hippo</em>
      object.</p><p>      Likewise you can call the <em>wash</em> method on the <em>hippo</em> with the
      URL <em>Zoo/LargeAnimals/hippo/wash</em>. In this case Zope acquires
      the <em>wash</em> method from the <em>LargeAnimals</em> folder.</p><p>      More complex arrangements are possible. Suppose you want to call the
      <em>vaccinate</em> script on the <em>hippo</em> object. What URL can you use? If
      you visit the URL <em>Zoo/LargeAnimals/hippo/vaccinate</em> Zope will not
      be able to find the <em>vaccinate</em> script since it isn't in any of
      the <em>hippo</em> object's containers.</p><p>      The solution is to give the path to the script as part of the
      URL. This way, when Zope uses acquisition to find the script it
      will find the right script as it backtracks along the URL. The
      URL to vaccinate the hippo is
      <em>Zoo/Vet/LargeAnimals/hippo/vaccinate</em>. Likewise, if you want to
      call the <em>vaccinate</em> script on the <em>kargarooMouse</em> object you
      should use the URL
      <em>Zoo/Vet/SmallAnimals/kargarooMouse/vaccinate</em>.</p><p>      Let's follow along as Zope traverses the URL
      <em>Zoo/Vet/LargeAnimals/hippo/vaccinate</em>. Zope starts in the root
      folder and looks for an object named <em>Zoo</em>. It moves to the
      <em>Zoo</em> folder and looks for an object named <em>Vet</em>. It moves to
      the <em>Vet</em> folder and looks for an object named
      <em>LargeAnimals</em>. The <em>Vet</em> folder doesn't contain an object with
      that name, but it can acquire the <em>LargeAnimals</em> folder from its
      container, <em>Zoo</em> folder. So it moves to the <em>LargeAnimals</em>
      folder and looks for an object named <em>hippo</em>.  It then moves to
      the <em>hippo</em> object and looks for an object named
      <em>vaccinate</em>. Since the <em>hippo</em> object does not contain a
      <em>vaccinate</em> object and neither do any of its containers, Zope
      backtracks along the URL path trying to find a <em>vaccinate</em>
      object. First it backs up to the <em>LargeAnimals</em> folder where
      <em>vaccinate</em> still can't be found. Then it backs up to the <em>Vet</em>
      folder.  Here it finds a <em>vaccinate</em> script in the <em>Vet</em>
      folder. Since Zope has now come to the end of the URL, it calls
      the <em>vaccinate</em> script in the context of the <em>hippo</em> object.</p><p>      When Zope looks for a sub-object during URL traversal, it first
      looks for the sub-object in the current object. If it can't find
      it in the current object it looks in the current object's
      containers. If it still can't find the sub-object, it backs up
      along the URL path and searches again. It continues this process
      until it either finds the object or raises an error if it can't
      be found.</p><p>      This is a very useful mechanism, and it allows you to be quite
      expressive when you compose URLs. The path that you tell Zope to
      take on its way to an object will determine how it uses
      acquisition to look up the object's scripts.</p><h3>    Calling Scripts from other Objects</h3>
<p>      You can call scripts from other objects. For example, it is
      common to call scripts from DTML Methods. </p><p>      As you saw in Chapter 8, "Variables and Advanced DTML", you can
      call Zope scripts from DTML with the <em>call</em> tag. For example:<pre>        &lt;dtml-call updateInfo&gt;</pre>
</p><p>      DTML will call the <em>updateInfo</em> script. You don't have to
      specify if the script is implemented in Perl, Python, or any
      other language (you can also call other DTML objects and SQL
      Methods this way).</p><p>      If the <em>updateInfo</em> script requires parameters, you must either
      choose a name for the DTML namespace binding (see Binding
      Variables below) so that the parameters will be looked up in the
      namespace, or you must pass the parameters in an expression,
      like this:<pre>        &lt;dtml-call expr=&quot;updateInfo(color='brown', pattern='spotted')&quot;&gt;</pre>
</p><p>      Calling scripts from Python and Perl works the same way, except
      that you must always pass script parameters when you call a
      script from Python or Perl. For example here's how you might
      call the <em>updateInfo</em> script from Python:<pre>        context.updateInfo(color='brown', 
                           pattern='spotted')</pre>
</p><p>      From Perl you could do the same thing using standard Perl
      semantics for calling scripts:<pre>        $self-&gt;updateInfo(color =&gt; 'brown', 
                          pattern =&gt; 'spotted');      </pre>
</p><p>      Each scripting language has a different way of writing a script call,
      but you don't have to know what language is used in the script you are
      calling. Effectively Zope objects can have scripts implemented in
      several different languages. But when you call a script you don't have
      to know how it's implemented, you just need to pass the appropriate
      parameters.</p><p>      Zope locates the scripts you call using acquisition the same way it
      does when calling scripts from the web.  Returning to our hippo
      feeding example of the last section, let's see how to vaccinate a hippo
      from Python and Perl. <a href="#8-2">Figure 8-2</a> shows a slightly updated object
      hierarchy that contains two scripts, <em>vaccinateHippo.py</em> and
      <em>vaccinateHippo.pl</em>.</p><p>      <a name="8-2"></a>
<img src="Figures/8-2.png" alt="A collection of objects and scripts">
<p><b>Figure 8-2</b> A collection of objects and scripts</p>
 </p><p>      Suppose <em>vaccinateHippo.py</em> is a Python script. Here's how you call the
      <em>vaccinate</em> script on the <em>hippo</em> object from Python:<pre>        context.Vet.LargeAnimals.hippo.vaccinate()</pre>
</p><p>      In other words you simply access the object using the same
      acquisition path as you would use if calling it from the
      web. Likewise in Perl you could say:<pre>        $self-&gt;Vet-&gt;LargeAnimals-&gt;hippo-&gt;vaccinate();</pre>
</p><p>      Using scripts from other scripts is very similar to calling
      scripts from the web. The semantics differ slightly but the same
      acquisition rules apply.  Later on in this chapter, you'll see more
      examples of how scripts in both Perl and Python work.</p><h3>    Passing Parameters to Scripts</h3>
<p>      All scripts can be passed parameters. A parameter gives a script
      more information about what to do. When you call a script from the
      web, Zope will try to find the script's parameters in the web
      request and pass them to your script. For example if you have a
      script with parameters <em>dolphin</em> and <em>REQUEST</em> Zope will
      look for <em>dolphin</em> in the web request, and will pass the request
      itself as the <em>REQUEST</em> parameter. In practical terms this means
      that it is easy to do form processing in your script. For example
      here is a form:<pre>        &lt;form action=&quot;actionScript&quot;&gt;
        Name &lt;input type=&quot;text&quot; name=&quot;name&quot;&gt;&lt;br&gt;
        Age &lt;input type=&quot;text&quot; name=&quot;age:int&quot;&gt;&lt;br&gt;
        &lt;input type=&quot;submit&quot;&gt;
        &lt;/form&gt;</pre>
</p><p>      You can easily process this form with a script named
      <em>actionScript</em> that includes <em>name</em> and <em>age</em> in its parameter
      list:<pre>        ## Script (Python) &quot;actionScript&quot;
        ##parameters=name, age
        ##
        &quot;Process form&quot;
        context.processName(name)
        context.processAge(age)
        return context.responseMessage()</pre>
</p><p>      There's no need to process the form manually to extract values
      from it. Form elements are passed as strings, or lists of
      strings in the case of check boxes, and multiple-select input.</p><p>      In addition to form variables, you can specify any request
      variables as script parameters. For example, to get access to the
      request and response objects just include <code>REQUEST</code> and <code>RESPONSE</code>
      in your list of parameters. Request variables are detailed more
      fully in Appendix B.</p><p>      One thing to note is that the <em>context</em> variable refers to the
      object that your script is called on. This works similarly in
      Perl-based Scripts, for example:<pre>        my $self = shift;
        $self-&gt;processName($name);
        $self-&gt;processAge($age);
        return $context-&gt;responseMessage();</pre>
</p><p>      In the Python version of the example, there is a subtle
      problem. You are probably expecting an integer rather than a
      string for age. You could manually convert the string to an
      integer using the Python <em>int</em> built-in:<pre>        age=int(age) # covert a string to an integer</pre>
</p><p>      But this manual conversion may be inconvenient. Zope provides a
      way for you to specify form input types in the form, rather than
      in the processing script. Instead of converting the <em>age</em> variable
      to an integer in the processing script, you can indicate that it
      is an integer in the form:<pre>        Age &lt;input type=&quot;text&quot; name=&quot;age:int&quot;&gt;</pre>
</p><p>      The <code>:int</code> appended to the form input name tells Zope to
      automatically convert the form input to an integer. If the user of
      your form types something that can't be converted to an integer
      (such as "22 going on 23") then Zope will raise an exception as
      shown in <a href="#8-3">Figure 8-3</a>.</p><p>      <a name="8-3"></a>
<img src="Figures/8-3.png" alt="Parameter conversion error">
<p><b>Figure 8-3</b> Parameter conversion error</p>
</p><p>      It's handy to have Zope catch conversion errors, but you may not
      like Zope's error messages. You should avoid using Zope's
      converters if you want to provide your own error messages.</p><p>      Zope can perform many parameter conversions. Here is a list of Zope's
      basic parameter converters.<dl>
<dt>        <em>boolean</em></dt>
<dd>Converts a variable to true or false. Variables
        that are 0, None, an empty string, or an empty sequence are
        false, all others are true.</dd>
<dt>        <em>int</em></dt>
<dd>Converts a variable to an integer.</dd>
<dt>        <em>long</em></dt>
<dd>Converts a variable to a long integer.</dd>
<dt>        <em>float</em></dt>
<dd>Converts a variable to a floating point number.</dd>
<dt>        <em>string</em></dt>
<dd>Converts a variable to a string. Most variables
        are strings already so this converter is seldom used.</dd>
<dt>        <em>text</em></dt>
<dd>Converts a variable to a string with normalized line
        breaks.  Different browsers on various platforms encode line
        endings differently, so this script makes sure the line endings are
        consistent, regardless of how they were encoded by the browser.</dd>
<dt>        <em>list</em></dt>
<dd>Converts a variable to a Python list.</dd>
<dt>        <em>tuple</em></dt>
<dd>Converts a variable to a Python tuple. A tuple is
        like a list, but cannot be modified.</dd>
<dt>        <em>tokens</em></dt>
<dd>Converts a string to a list by breaking it on white
        spaces.</dd>
<dt>        <em>lines</em></dt>
<dd>Converts a string to a list by breaking it on new
        lines.</dd>
<dt>        <em>date</em></dt>
<dd>Converts a string to a <em>DateTime</em> object. The formats
        accepted are fairly flexible, for example <code>10/16/2000</code>,
        <code>12:01:13 pm</code>.</dd>
<dt>        <em>required</em></dt>
<dd>Raises an exception if the variable is not present.</dd>
<dt>        <em>ignore_empty</em></dt>
<dd>Excludes the variable from the request if
        the variable is an empty string.</dd>
</dl>
</p><p>      These converters all work in more or less the same way to coerce a
      string form variable into a specific type. You may recognize these
      converters from Chapter 3, "Using Basic Zope Objects",  where we
      discussed properties. These converters are used by Zope's property
      facility to convert properties to the right type.</p><p>      The <em>list</em> and <em>tuple</em> converters can be used in combination with other
      converters.  This allows you to apply additional converters to each
      element of the list or tuple.  Consider this form:<pre>        &lt;form action=&quot;processTimes&quot;&gt; 

        &lt;p&gt;I would prefer not to be disturbed at the following
        times:&lt;/p&gt;

        &lt;input type=&quot;checkbox&quot; name=&quot;disturb_times:list:date&quot;
        value=&quot;12:00 AM&quot;&gt; Midnight&lt;br&gt;

        &lt;input type=&quot;checkbox&quot; name=&quot;disturb_times:list:date&quot;
        value=&quot;01:00 AM&quot;&gt; 1:00 AM&lt;br&gt;

        &lt;input type=&quot;checkbox&quot; name=&quot;disturb_times:list:date&quot;
        value=&quot;02:00 AM&quot;&gt; 2:00 AM&lt;br&gt;

        &lt;input type=&quot;checkbox&quot; name=&quot;disturb_times:list:date&quot;
        value=&quot;03:00 AM&quot;&gt; 3:00 AM&lt;br&gt;

        &lt;input type=&quot;checkbox&quot; name=&quot;disturb_times:list:date&quot;
        value=&quot;04:00 AM&quot;&gt; 4:00 AM&lt;br&gt;

        &lt;input type=&quot;submit&quot;&gt;
        &lt;/form&gt;</pre>
</p><p>      By using the <em>list</em> and <em>date</em> converters together Zope will
      convert each selected time to a date and then combine all selected
      dates into a list named <em>disturb_times</em>.</p><p>      A more complex type of form conversion is to convert a series of inputs
      into records. Records are structures that have attributes. Using
      records you can combine a number of form inputs into one variable with
      attributes.  The available record converters are:<dl>
<dt>        <em>record</em></dt>
<dd>Converts a variable to a record attribute.</dd>
<dt>        <em>records</em></dt>
<dd>Converts a variable to a record attribute in a list of
        records.</dd>
<dt>        <em>default</em></dt>
<dd>Provides a default value for a record attribute if the
        variable is empty.</dd>
<dt>        <em>ignore_empty</em></dt>
<dd>Skips a record attribute if the variable is empty.</dd>
</dl>
</p><p>      Here are some examples of how these converters are used:<pre>        &lt;form action=&quot;processPerson&quot;&gt;

        First Name &lt;input type=&quot;text&quot; name=&quot;person.fname:record&quot;&gt;&lt;br&gt;
        Last Name &lt;input type=&quot;text&quot; name=&quot;person.lname:record&quot;&gt;&lt;br&gt;
        Age &lt;input type=&quot;text&quot; name=&quot;person.age:record:int&quot;&gt;&lt;br&gt;

        &lt;input type=&quot;submit&quot;&gt;
        &lt;/form&gt;</pre>
</p><p>      This form will call the <em>processPerson</em> script with one
      parameter, <em>person</em>. The <em>person</em> variable will have <em>fname</em>,
      <em>lname</em> and <em>age</em> attributes. Here's an example of how you might
      use the <em>person</em> variable in your <em>processPerson</em> script:<pre>        ## Script (Python) &quot;processPerson&quot;
        ##parameters=person
        ##
        &quot; process a person record &quot;
        full_name=&quot;%s %s&quot; % (person.fname, person.lname)
        if person.age &lt; 21:
            return &quot;Sorry, %s. You are not old enough to adopt an aardvark.&quot; % full_name
        return &quot;Thanks, %s. Your aardvark is on its way.&quot; % full_name</pre>
</p><p>      The <em>records</em> converter works like the <em>record</em> converter except
      that it produces a list of records, rather than just one. Here's
      an example form:<pre>        &lt;form action=&quot;processPeople&quot;&gt;

        &lt;p&gt;Please, enter information about one or more of your next of
        kin.&lt;/p&gt;

        &lt;p&gt;First Name &lt;input type=&quot;text&quot; name=&quot;people.fname:records&quot;&gt;
        Last Name &lt;input type=&quot;text&quot; name=&quot;people.lname:records&quot;&gt;&lt;/p&gt;

        &lt;p&gt;First Name &lt;input type=&quot;text&quot; name=&quot;people.fname:records&quot;&gt;
        Last Name &lt;input type=&quot;text&quot; name=&quot;people.lname:records&quot;&gt;&lt;/p&gt;

        &lt;p&gt;First Name &lt;input type=&quot;text&quot; name=&quot;people.fname:records&quot;&gt;
        Last Name &lt;input type=&quot;text&quot; name=&quot;people.lname:records&quot;&gt;&lt;/p&gt;

        &lt;input type=&quot;submit&quot;&gt;
        &lt;/form&gt;    </pre>
</p><p>      This form will call the <em>processPeople</em> script with a variable
      called <em>people</em> that is a list of records. Each record will have
      <em>fname</em> and <em>lname</em> attributes.</p><p>      Another useful parameter conversion uses form variables to rewrite the
      action of the form. This allows you to submit a form to different
      scripts depending on how the form is filled out. This is most useful in
      the case of a form with multiple submit buttons. Zope's action
      converters are:<dl>
<dt>        <em>action</em></dt>
<dd>Changes the action of the form. This is mostly
        useful in the case where you have multiple submit buttons on one
        form.  Each button can be assigned to a script that gets called
        when that button is clicked to submit the form.</dd>
<dt>        <em>default_action</em></dt>
<dd>Changes the action script of the form when
        no other <em>method</em> converter is found.</dd>
</dl>
</p><p>      Here's an example form that uses action converters:<pre>        &lt;form action=&quot;&quot;&gt;

        &lt;p&gt;Select one or more employees&lt;/p&gt;

        &lt;input type=&quot;checkbox&quot; name=&quot;employees:list&quot; value=&quot;Larry&quot;&gt; Larry&lt;br&gt;
        &lt;input type=&quot;checkbox&quot; name=&quot;employees:list&quot; value=&quot;Simon&quot;&gt; Simon&lt;br&gt;
        &lt;input type=&quot;checkbox&quot; name=&quot;employees:list&quot; value=&quot;Rene&quot;&gt; Rene&lt;br&gt;

        &lt;input type=&quot;submit&quot; name=&quot;fireEmployees:action&quot;
        value=&quot;Fire!&quot;&gt;&lt;br&gt;

        &lt;input type=&quot;submit&quot; name=&quot;promoteEmployees:action&quot;
        value=&quot;Promote!&quot;&gt;

        &lt;/form&gt;</pre>
</p><p>      This form will call either the <em>fireEmployees</em> or the
      <em>promoteEmployees</em> script depending on which of the two submit
      buttons is used.  Notice also how it builds a list of employees
      with the <em>list</em> converter.  Form converters can be very useful
      when designing Zope applications.</p><h2>  Script Security</h2>
<p>    All scripts that can be edited through the web are subject to
    Zope's standard security policies. The only scripts that are not
    subject to these security restrictions are scripts that must be
    edited through the filesystem. These unrestricted scripts include
    Python and Perl <em>External Methods</em>.</p><p>    Chapter 7, "Users and Security" covers security in more
    detail. You should consult the <em>Roles of Executable Objects</em> and
    <em>Proxy Roles</em> sections for more information on how scripts are
    restricted by Zope security constraints.</p><h2>  The Zope API</h2>
<p>    One of the main reasons to script Zope is to get convenient access to
    the Zope API (Application Programmer Interface). The Zope API describes
    built-in actions that can be called on Zope objects. You can examine
    the Zope API in the help system, as shown in <a href="#8-4">Figure 8-4</a>.</p><p>    <a name="8-4"></a>
<img src="Figures/8-4.png" alt="Zope API Documentation">
<p><b>Figure 8-4</b> Zope API Documentation</p>
</p><p>    Suppose you'd like to have a script that takes a file you upload
    from a form and creates a Zope File object in a folder. To do this
    you need to know a number of Zope API actions. It's easy enough to
    read files in Python or Perl, but once you have the file you need
    to know what actions to call to create a new File object in a
    Folder.</p><p>    There are many other things that you might like to script using the
    Zope API. Any management task that you can perform through the web can
    be scripted using the Zope API. This includes creating, modifying and
    deleting Zope objects. You can even perform maintenance tasks, like
    restarting Zope and packing the Zope database.</p><p>    The Zope API is documented in Appendix B, "API Reference" as well as
    in the Zope online help. The API documentation shows you which classes
    inherit from which other classes. For example <em>Folder</em> inherits from
    <em>ObjectManager</em>. This means that Folder objects have all the
    actions listed in the <em>ObjectManager</em> section of the API
    reference.</p><h2>  Using Python-based Scripts</h2>
<p>    Earlier in this chapter you saw some examples of scripts.  Now
    let's take a look at scripts in more detail.</p><h3>    The Python Language</h3>
<p>      <a href="http://www.python.org/">Python</a> is a high-level, object oriented
      scripting language.  Most of Zope is written in Python. Many
      folks like Python because of its clarity, simplicity and ability
      to scale to large projects. </p><p>      There are many resources available for learning Python. The
      python.org web site has lots of Python documentation including a
      <a href="http://www.python.org/doc/current/tut/tut.html">tutorial</a> by
      Python's Creator, Guido van Rossum. </p><p>      Python comes with a rich set of modules and packages. You can
      find out more about the <a href="http://www.python.org/doc/current/lib/lib.html">Python standard
      library</a> at the
      python.org web site.</p><p>      Another highly respected source for reference material is <em>Python
      Essential Reference</em> by David Beazley published by New Riders.</p><h3>    Creating Python-based Scripts</h3>
<p>      To create a Python-based Script choose <em>Script (Python)</em> from the
      Product add list. Name the script <em>hello</em>, and click the <em>Add and
      Edit</em> button. You should now see the <em>Edit</em> view of your script as
      shown in <a href="#8-5">Figure 8-5</a>.</p><p>      <a name="8-5"></a>
<img src="Figures/8-5.png" alt="Script editing view">
<p><b>Figure 8-5</b> Script editing view</p>
</p><p>      This screen allows you to control the parameters and body of your
      script. You can enter your script's parameters in the <em>parameter
      list</em> field. Type the body of your script in the text
      area at the bottom of the screen.</p><p>      Enter <em>name="World"</em> into the <em>parameter list</em> field, and type:<pre>        return &quot;Hello %s.&quot; % name</pre>
</p><p>      in the body of the script. This is equivalent to this in
      standard Python syntax:<pre>        def hello(name=&quot;World&quot;):
            return &quot;Hello %s.&quot; % name</pre>
</p><p>      You can now test this script by going to the <em>Test</em> tab as shown
      in <a href="#8-6">Figure 8-6</a>.</p><p>      <a name="8-6"></a>
<img src="Figures/8-6.png" alt="Testing a Script">
<p><b>Figure 8-6</b> Testing a Script</p>
</p><p>      Leave the <em>name</em> field blank and click the <em>Run Script</em>
      button. Zope should return "Hello World." Now go back and try
      entering your name in the <em>Value</em> field and click the <em>Run
      Script</em> button. Zope should now say hello to you.</p><p>      Since scripts are called on Zope objects, you can get access to
      Zope objects via the <em>context</em> variable. For example, this script
      returns the number of objects contained by a given Zope object:<pre>        ## Script (Python) &quot;numberOfObjects
        ##
        return len(context.objectIds())</pre>
</p><p>      The script calls <code>context.objectIds()</code> to find out the number of
      contained objects. When you call this script on a given Zope
      object, the context variable is bound to the context object. So if
      you called this script by visiting the URL
      <em>FolderA/FolderB/numberOfObjects</em> the <em>context</em> parameter would
      refer to the <em>FolderB</em> object.</p><p>      When writing your logic in Python you'll typically want to query
      Zope objects, call other scripts and return reports. For example,
      suppose you want to implement a simple workflow system in which
      various Zope objects are tagged with properties that indicate
      their status. You might want to produce reports that summarize
      which objects are in which state. You can use Python to query
      objects and test their properties. For example, here is a script
      named <em>objectsForStatus</em> with one parameter, <em>status</em>:<pre>        ## Script (Python) &quot;objectsForStatus&quot;
        ##parameters=status
        ##
        &quot;&quot;&quot;
        Returns all sub-objects that have a given status
        property.
        &quot;&quot;&quot;
        results=[]
        for object in context.objectValues():
            if object.getProperty('status') == status:
                results.append(object)
        return results</pre>
</p><p>      This script loops through an object's sub-objects and returns
      all the sub-objects that have a <em>status</em> property with a given
      value. </p><p>      You could then use this script from DTML to email reports. For
      example:<pre>        &lt;dtml-sendmail&gt;
        To: &lt;dtml-var ResponsiblePerson&gt;
        Subject: Pending Objects

        These objects are pending and need attention.

        &lt;dtml-in expr=&quot;objectsForStatus('Pending')&quot;&gt;
        &lt;dtml-var title_or_id&gt; (&lt;dtml-var absolute_url&gt;)
        &lt;/dtml-in&gt;
        &lt;/dtml-sendmail&gt;</pre>
</p><p>      This example shows how you can use DTML for presentation or
      report formatting, while Python handles the logic. This is a
      very important pattern, that you'll see over and over in Zope. </p><h3>    String Processing</h3>
<p>      One common use for scripts is to do string processing. Python has a
      number of standard modules for string processing. You cannot do regular
      expression processing from Python-based Scripts, but you do have access
      to the <em>string</em> module. You have access to the <em>string</em> module from
      DTML as well, but it is much easier to use from Python. Suppose you
      want to change all the occurrences of a given word in a DTML
      Document. Here's a script, <em>replaceWord</em>, that accepts two arguments,
      <em>word</em> and <em>replacement</em>.  This will change all the occurrences of a
      given word in a DTML Document:<pre>        ## Script (Python) &quot;replaceWord&quot;
        ##parameters=word, replacement
        ##
        &quot;&quot;&quot;
        Replaces all the occurrences of a word with a
        replacement word in the source text of a DTML
        Document. Call this script on a DTML Document to use
        it. 

        Note: you'll need permission to edit a document to
        call this script on the document.
        &quot;&quot;&quot;
        import string
        text=context.document_src()
        text=string.replace(text, word, replacement)
        context.manage_edit(text, context.title)</pre>
</p><p>      You can call this script from the web on a DTML Document to
      change the source of the document. For example, the URL
      <em>Swamp/replaceWord?word=Alligator&replacement=Crocodile</em> would
      call the <em>replaceWord</em> script on a document named <em>Swamp</em> and
      would replace all occurrences of the word <em>Alligator</em> with
      <em>Crocodile</em>.</p><p>      The <em>string</em> module that you can access via scripts does not
      have all the features available in the standard Python string
      module. These limitations are imposed for security reasons. See
      Appendix A for more information on the <em>string</em> module.</p><p>      One thing that you might be tempted to do with scripts is to use
      Python to search for objects that contain a given word in their
      text or as a property. You can do this, but Zope has a much
      better facility for this kind of work, the <em>Catalog</em>. See
      Chapter 11, "Searching and Categorizing Content" for more
      information on searching with Catalogs.</p><h3>    Doing Math</h3>
<p>      Another common use of scripts is to perform mathematical
      calculations which would be unwieldy from DTML. The <em>math</em> and
      <em>random</em> modules give you access from Python to many math
      functions. These modules are standard Python services as
      described on the Python.org web site.<dl>
<dt>        <a href="http://www.python.org/doc/current/lib/module-math.html">math</a></dt>
<dd>Mathematical functions such as <em>sin</em> and <em>cos</em>.</dd>
<dt>        <a href="http://www.python.org/doc/current/lib/module-random.html">random</a></dt>
<dd>Pseudo random number generation functions.</dd>
</dl>
</p><p>      One interesting function of the <em>random</em> module is the
      <em>choice</em> function that returns a random selection from a
      sequence of objects. Here's an example of how to use this
      function in a script called <em>randomImage</em>:<pre>        ## Script (Python) &quot;randomImage&quot;
        ##
        &quot;&quot;&quot;
        When called on a Folder that contains Image objects this
        script returns a random image.
        &quot;&quot;&quot;
        import random
        return random.choice(context.objectValues('Image'))</pre>
</p><p>      Suppose you had a Folder named <em>Images</em> that contained a
      number of images. You could display a random image from the
      folder in DTML like so:<pre>        &lt;dtml-with Images&gt;
          &lt;dtml-var randomImage&gt;
        &lt;/dtml-with&gt;</pre>
</p><p>      This DTML calls the <em>randomImage</em> script on the <em>Images</em>
      folder. The result is a HTML <em>IMG</em> tag that references a
      random image in the <em>Images</em> Folder.</p><h3>    Binding Variables</h3>
<p>      A set of special variables is created whenever a Python-based Script is
      called.  These variables, defined on the <em>Bindings</em> view, are used
      by your script to access other Zope objects and scripts.</p><p>      By default, the names of these binding variables are set to
      reasonable values and you should not need to change them.  They
      are explained here so that you know how each special variable
      works, and how you can use these variables in your scripts.<dl>
<dt>        Context</dt>
<dd>The <em>Context</em> binding defaults to the name
        <em>context</em>.  This variable refers to the object that the
        script is called on.</dd>
<dt>        Container</dt>
<dd>The <em>Container</em> binding defaults to the name
        <em>container</em>.  This variable refers to the folder that the
        script is defined in.</dd>
<dt>        Script</dt>
<dd>The <em>Script</em> binding defaults to the name
        <em>script</em>.  This variable refers to the script object itself.</dd>
<dt>        Namespace</dt>
<dd>The <em>Namespace</em> binding is left blank by default.
        This is an advanced variable that you will not need for any of
        the examples in this book.  If your script is called from a
        DTML Method, and you have chosen a name for this binding, then
        the named variable contains the DTML namespace explained in
        Chapter 8, "Variables and Advanced DTML".  Also, if this
        binding is set, the script will search for its parameters in
        the DTML namespace when called from DTML without explicitly
        passing any arguments.</dd>
<dt>        Subpath</dt>
<dd>The <em>Subpath</em> binding defaults to the name
        <em>traverse_subpath</em>.  This is an advanced variable that you
        will not need for any of the examples in this book.  If your
        script is traversed, meaning that other path elements
        follow it in a URL, then those path elements are placed in a
        list, from left to right, in this variable.</dd>
</dl>
</p><p>      If you edit your scripts via FTP, you'll notice that these
      bindings are listed in comments at the top of your script
      files. For example:<pre>        ## Script (Python) &quot;example&quot;
        ##bind container=container
        ##bind context=context
        ##bind namespace=
        ##bind script=script
        ##bind subpath=traverse_subpath
        ##parameters=name, age
        ##title=
        ##
        return &quot;Hello %s you are %d years old.&quot; % (name, age)</pre>
</p><p>      You can change your script's bindings by changing these comments
      and then uploading your script.</p><h3>    Print Statement Support</h3>
<p>      Python-based Scripts have a special facility to help you print
      information. Normally printed data is sent to standard output and is
      displayed on the console. This is not practical for a server
      application like Zope since most of the time you do not have access to
      the server's console. Scripts allow you to use print anyway and to
      retrieve what you printed with the special variable <em>printed</em>. For
      example:<pre>        ## Script (Python) &quot;printExample&quot;
        ##
        for word in ('Zope', 'on', 'a', 'rope'):
            print word
        return printed</pre>
</p><p>      This script will return:<pre>        Zope
        on
        a
        rope</pre>
</p><p>      The reason that there is a line break in between each word is
      that Python adds a new line after every string that is
      printed.</p><p>      You might want to use the print statement to perform simple
      debugging in your scripts. For more complex output
      control you probably should manage things yourself by
      accumulating data, modifying it and returning it manually rather
      than relying on the print statement.</p><h3>    Security Restrictions</h3>
<p>      Scripts are restricted in order to limit their ability
      to do harm. What could be harmful? In general, scripts
      keep you from accessing private Zope objects, making harmful
      changes to Zope objects, hurting the Zope process itself, and
      accessing the server Zope is running on. These restrictions
      are implemented through a collection of limits on what your
      scripts can do.<dl>
<dt>        Loop limits</dt>
<dd>Scripts cannot create infinite loops. If your script
        loops a very large number of times Zope will raise an error. This
        restriction covers all kinds of loops including <em>for</em> and <em>while</em>
        loops. The reason for this restriction is to limit your ability to
        hang Zope by creating an infinite loop.</dd>
<dt>        Import limits</dt>
<dd>Scripts cannot import arbitrary packages and
        modules. You are limited to importing the
        <em>Products.PythonScripts.standard</em> utility module, the
        <em>AccessControl</em> module, those modules available via DTML
        (<em>string</em>, <em>random</em>, <em>math</em>, <em>sequence</em>), and modules which
        have been specifically made available to scripts by product
        authors.  See Appendix B, "API Reference" for more information
        on these modules.  If you want to be able to import any Python
        module, use an External Method, as described later in the
        chapter.</dd>
<dt>        Access limits</dt>
<dd>You are restricted by standard Zope security
        policies when accessing objects. In other words the user
        executing the script is checked for authorization when
        accessing objects. As with all executable objects you can
        modify the effective roles a user has when calling a script
        using <em>Proxy Roles</em> (see Chapter 7, "Users and Security", for
        more information.) In addition, you cannot access objects
        whose names begin with underscore, since Zope considers these
        objects to be private.</dd>
<dt>        Writing limits</dt>
<dd>In general you cannot change Zope object
        attributes using scripts. You should call scripts on Zope objects
        to change them, rather than directly changing instance attributes.</dd>
</dl>
</p><p>      Despite these limits, a determined user could use large amounts
      of CPU time and memory using Python-based Scripts. So malicious
      scripts could constitute a kind of denial of service attack by
      using lots of resources. These are difficult problems to solve
      and DTML suffers from the same potential for abuse. As with
      DTML, you probably shouldn't grant access to scripts to
      untrusted people.</p><h3>    Built-in Functions</h3>
<p>      Python-based Scripts give you a slightly different menu of built-ins
      than you find in normal Python. Most of the changes are designed to
      keep you from performing unsafe actions. For example, the <em>open</em>
      function is not available, which keeps you from being able to access
      the filesystem. To partially make up for some missing built-ins a few
      extra functions are available.</p><p>      These restricted built-ins work the same as standard Python
      built-ins: <em>None</em>, <em>abs</em>, <em>apply</em>, <em>callable</em>, <em>chr</em>, <em>cmp</em>,
      <em>complex</em>, <em>delattr</em>, <em>divmod</em>, <em>filter</em>, <em>float</em>, <em>getattr</em>,
      <em>hash</em>, <em>hex</em>, <em>int</em>, <em>isinstance</em>, <em>issubclass</em>, <em>list</em>, <em>len</em>,
      <em>long</em>, <em>map</em>, <em>max</em>, <em>min</em>, <em>oct</em>, <em>ord</em>, <em>repr</em>, <em>round</em>,
      <em>setattr</em>, <em>str</em>, <em>tuple</em>.  For more information on what these
      built-ins do, see the online <a href="http://www.python.org/doc/">Python
      Documentation</a>.</p><p>      The <em>range</em> and <em>pow</em> functions are available and work the same
      way they do in standard Python; however, they are limited to
      keep them from generating very large numbers and sequences. This
      limitation helps protect against denial of service attacks as
      described previously.</p><p>      In addition, these DTML utility functions are available:
      <em>DateTime</em>, and <em>test</em>. See Appendix A, "DTML Reference" for
      more information on these functions.</p><p>      Finally to make up for the lack of a <em>type</em> function, there is a
      <em>same_type</em> function that compares the type of two or more
      objects, returning true if they are of the same type.  So
      instead of saying:<pre>        if type(foo) == type([]):
            return &quot;foo is a list&quot;</pre>
</p><p>      to check if <code>foo</code> is a list, you would instead use the <em>same_type</em>
      function to check this:<pre>        if same_type(foo, []):
            return &quot;foo is a list&quot;</pre>
</p><p>      Now let's take a look at <em>External Methods</em> which provide more
      power and less restrictions than Python-based Scripts.</p><h2>  Using External Methods</h2>
<p>    Sometimes the security constraints imposed by scripts get
    in your way. For example, you might want to read files from disk,
    or access the network, or use some advanced libraries for things
    like regular expressions or image processing. In these cases
    you'll want to use <em>External Methods</em>.</p><p>    To create and edit External Methods you need access
    to the filesystem. This makes editing these scripts more
    cumbersome since you can't edit them right in your web
    browser. However requiring access to the server's filesystem
    provides an important security control. If a user has access
    to a servers filesystem they already have the ability to harm
    Zope. So by requiring that unrestricted scripts be edited on
    the filesystem Zope ensures that only people who are already
    trusted have access.</p><p>    Unrestricted scripts are created and edited in files on the
    Zope server in the <em>Extensions</em> directory. This directory is
    located in the top-level Zope directory. Alternately you can
    create and edit unrestricted scripts in an <em>Extensions</em>
    directory inside an installed Zope product directory.</p><p>    Create a file named <em>Example.py</em> in the Zope <em>Extensions</em>
    directory on your server. In the <em>Example.py</em> file, enter the
    following code:<pre>      def hello(name=&quot;World&quot;):
          return &quot;Hello %s.&quot; % name </pre>
</p><p>    You've created a Python function in a Python module. Now let's use
    this function in the External Method.</p><p>    You manage External Methods the same way you manage restricted
    scripts with the exception that you cannot edit the script itself
    through the web. Instead of editing code you must tell Zope where
    to find your code on the filesystem. You do this by specifying the
    name of your Python file and the name of the function within the
    module.</p><p>    To create an External Method choose <em>External Method</em> from the
    product add list. You will be taken to an add form where you must
    provide an id. Type "hello" into the <em>Id</em> field and "hello" in the
    <em>Function name</em> field and "Example" in the <em>Module name</em> field and
    click the <em>Add</em> button.  You should now see a new External Method
    object in your folder. Click on it. You should be taken to the
    <em>Properties</em> view of your new External Method as shown in <a href="#8-7">Figure 8-7</a>.</p><p>    <a name="8-7"></a>
<img src="Figures/8-7.png" alt="External Method Properties view">
<p><b>Figure 8-7</b> External Method Properties view</p>
</p><p>    Now test your new script by going to the <em>Test</em> view. You should
    see a greeting. You can pass different names to the script by
    specifying them in the URL. For example,
    <em>hello?name=Spanish+Inquisition</em>.</p><p>    This example is exactly the same as the hello world example
    that you saw for using scripts. In fact for
    simple string processing tasks like this restricted scripts
    offer a better solution since they are easier to work with.</p><p>    The main reasons to use an unrestricted script are to access
    the filesystem or network or to use Python packages that are
    not available to restricted scripts.</p><p>    Here's an example External Method that uses the Python Imaging
    Library (PIL) to create a thumbnail version of an existing Image
    object in a Folder.  Enter the following code in a file named
    <em>Thumbnail.py</em> in the <em>Extensions</em> directory:<pre>      def makeThumbnail(self, original_id, size=200):
          &quot;&quot;&quot;
          Makes a thumbnail image given an image Id when called on a Zope
          folder.

          The thumbnail is a Zope image object that is a small JPG
          representation of the original image. The thumbnail has a
          'original_id' property set to the id of the full size image
          object.
          &quot;&quot;&quot;

          from PIL import Image
          from StringIO import StringIO
          import os.path

          # create a thumbnail image file
          original_image=getattr(self, original_id)
          original_file=StringIO(str(original_image.data))
          image=Image.open(original_file)
          image=image.convert('RGB')
          image.thumbnail((size,size))
          thumbnail_file=StringIO()
          image.save(thumbnail_file, &quot;JPEG&quot;) 
          thumbnail_file.seek(0)

          # create an id for the thumbnail
          path, ext=os.path.splitext(original_id)
          thumbnail_id=path + '.thumb.jpg'

          # if there's and old thumbnail, delete it
          if thumbnail_id in self.objectIds():
              self.manage_delObjects([thumbnail_id])

          # create the Zope image object
          self.manage_addProduct['OFSP'].manage_addImage(thumbnail_id,
                                                         thumbnail_file,
                                                         'thumbnail image')
          thumbnail_image=getattr(self, thumbnail_id)

          # set the 'originial_id' property
          thumbnail_image.manage_addProperty('original_id', original_id, 'string')</pre>
</p><p>    You must have PIL installed for this example to work. See the
    <a href="//http://www.pythonworks.com/products/pil">PythonWorks website</a>
    for more information on PIL.  To use this code create an External
    Method named <em>makeThumbnail</em> that uses the <em>makeThumbnail</em>
    function in the <em>Thumbnail</em> module.</p><p>    Now you have a method that will create a thumbnail image. You can
    call it on a Folder with a URL like
    <em>ImageFolder/makeThumbnail?original_id=Horse.gif</em> This would
    create a thumbnail image named <code>Horse.thumb.jpg</code>.</p><p>    You can use a script to loop through all the images in a folder and
    create thumbnail images for them. Create a script named
    <em>makeThumbnails</em>:<pre>      ## Script (Python) &quot;makeThumbnails&quot;
      ##
      for image_id in context.objectIds('Image'):
          context.makeThumbnail(image_id)</pre>
</p><p>    This will loop through all the images in a folder and create a
    thumbnail for each one.</p><p>    Now call this script on a folder with images in it. It will create a
    thumbnail image for each contained image. Try calling the
    <em>makeThumbnails</em> script on the folder again and you'll notice it created
    thumbnails of your thumbnails. This is no good. You need to change the
    <em>makeThumbnails</em> script to recognize existing thumbnail images and not
    make thumbnails of them. Since all thumbnail images have an
    <em>original_id</em> property you can check for that property as a way of
    distinguishing between thumbnails and normal images:<pre>      ## Script (Python) &quot;makeThumbnails&quot;
      ##
      for image in context.objectValues('Image'):
          if not image.hasProperty('original_id'):
              context.makeThumbnail(image.getId())</pre>
</p><p>    Delete all the thumbnail images in your folder and try calling your
    updated <em>makeThumbnails</em> script on the folder. It seems to work
    correctly now.</p><p>    Now with a little DTML you can glue your script and External Method
    together. Create a DTML Method called <em>displayThumbnails</em>:<pre>      &lt;dtml-var standard_html_header&gt;

      &lt;dtml-if updateThumbnails&gt;
        &lt;dtml-call makeThumbnails&gt;
      &lt;/dtml-if&gt;

      &lt;h2&gt;Thumbnails&lt;/h2&gt;

      &lt;table&gt;&lt;tr valign=&quot;top&quot;&gt;

      &lt;dtml-in expr=&quot;objectValues('Image')&quot;&gt;
        &lt;dtml-if original_id&gt;
          &lt;td&gt;
            &lt;a href=&quot;&amp;dtml-original_id;&quot;&gt;&lt;dtml-var sequence-item&gt;&lt;/a&gt;&lt;br&gt;
            &lt;dtml-var original_id&gt;
          &lt;/td&gt; 
        &lt;/dtml-if&gt;
      &lt;/dtml-in&gt;

      &lt;/tr&gt;&lt;/table&gt;

      &lt;form&gt;
      &lt;input type=&quot;submit&quot; name=&quot;updateThumbnails&quot; value=&quot;Update Thumbnails&quot;&gt;
      &lt;/form&gt;

      &lt;dtml-var standard_html_footer&gt;</pre>
</p><p>    When you call this DTML Method on a folder it will loop through all the
    images in the folder and display all the thumbnail images and link them
    to the originals as shown in <a href="#8-8">Figure 8-8</a>.</p><p>    <a name="8-8"></a>
<img src="Figures/8-8.png" alt="Displaying thumbnail images">
<p><b>Figure 8-8</b> Displaying thumbnail images</p>
</p><p>    This DTML Method also includes a form that allows you to update the
    thumbnail images. If you add, delete or change the images in your
    folder you can use this form to update your thumbnails.</p><p>    This example shows how to use scripts, External Methods and DTML
    together. Python takes care of the logic while the DTML handles
    presentation. Your External Methods handle external packages while your
    scripts do simple processing of Zope objects.</p><h3>    Processing XML with External Methods</h3>
<p>      You can use External Methods to do darn near anything. One interesting
      thing that you can do is to communicate using XML. You can generate and
      process XML with External Methods.</p><p>      Zope already understands some kinds of XML messages such as
      XML-RPC and WebDAV. As you create web applications that communicate
      with other systems you may want to have the ability to receive XML
      messages. You can receive XML a number of ways: you can read XML files
      from the file system or over the network, or you can define scripts
      that take XML arguments which can be called by remote systems.</p><p>      Once you have received an XML message you must process the XML to find
      out what it means and how to act on it.  Let's take a quick look at how
      you might parse XML manually using Python. Suppose you want to connect
      your web application to a <a href="http://www.jabber.com/">Jabber</a> chat
      server. You might want to allow users to message you and receive
      dynamic responses based on the status of your web application. For
      example suppose you want to allow users to check the status of animals
      using instant messaging. Your application should respond to XML instant
      messages like this:<pre>        &lt;message to=&quot;cage_monitor@zopezoo.org&quot; from=&quot;user@host.com&quot;&gt;
          &lt;body&gt;monkey food status&lt;/body&gt;
        &lt;/message&gt;</pre>
</p><p>      You could scan the body of the message for commands, call a script
      and return responses like this:<pre>        &lt;message to=&quot;user@host.com&quot; from=&quot;cage_monitor@zopezoo.org&quot;&gt;
          &lt;body&gt;Monkeys were last fed at 3:15&lt;/body&gt;
        &lt;/message&gt;</pre>
</p><p>      Here is a sketch of how you could implement this XML messaging
      facility in your web application using an External Method:<pre>        # Uses Python 2.x standard xml processing packages.  See
        # http://www.python.org/doc/current/lib/module-xml.sax.html for
        # information about Python's SAX (Simple API for XML) support If
        # you are using Python 1.5.2 you can get the PyXML package. See
        # http://pyxml.sourceforge.net for more information about PyXML.

        from xml.sax import parseString
        from xml.sax.handler import ContentHandler

        class MessageHandler(ContentHandler):
            &quot;&quot;&quot;
            SAX message handler class

            Extracts a message's to, from, and body
            &quot;&quot;&quot;

            inbody=0
            body=&quot;&quot;

            def startElement(self, name, attrs):
                if name==&quot;message&quot;:
                    self.recipient=attrs['to']
                    self.sender=attrs['from']
                elif name==&quot;body&quot;:
                    self.inbody=1

            def endElement(self, name):
                if name==&quot;body&quot;:
                    self.inbody=0

            def characters(self, content):
                if self.inbody:
                    self.body=self.body + content

        def receiveMessage(self, message):
            &quot;&quot;&quot;
            Called by a Jabber server
            &quot;&quot;&quot;
            handler=MessageHandler()
            parseString(message, handler)

            # call a script that returns a response string
            # given a message body string
            response_body=self.getResponse(handler.body)

            # create a response XML message
            response_message=&quot;&quot;&quot;
              &lt;message to=&quot;%s&quot; from=&quot;%s&quot;&gt;
                &lt;body&gt;%s&lt;/body&gt;
              &lt;/message&gt;&quot;&quot;&quot; % (handler.sender, handler.recipient, response_body)

            # return it to the server
            return response_message</pre>
</p><p>      The <em>receiveMessage</em> External Method uses Python's SAX (Simple API
      for XML) package to parse the XML message. The <em>MessageHandler</em>
      class receives callbacks as Python parses the message. The handler
      saves information its interested in. The External Method uses the
      handler class by creating an instance of it, and passing it to the
      <em>parseString</em> function. It then figures out a response message by
      calling <em>getResponse</em> with the message body. The <em>getResponse</em>
      script (which is not shown here) presumably scans the body for
      commands, queries the web applications state and returns some
      response. The <em>receiveMessage</em> method then creates an XML message
      using response and the sender information and returns it.</p><p>      The remote server would use this External Method by calling the
      <em>receiveMessage</em> method using the standard HTTP POST
      command. Voila, you've implemented a custom XML chat server that
      runs over HTTP.</p><h3>    External Method Gotchas</h3>
<p>      While you are essentially unrestricted in what you can do in an
      External Method, there are still some things that
      are hard to do.</p><p>      While your Python code can do as it pleases if you want to
      work with the Zope framework you need to respect its
      rules. While programming with the Zope framework is too
      advanced a topic to cover here, there are a few things that
      should be aware of.</p><p>      Problems can occur if you hand instances of your own classes to
      Zope and expect them to work like Zope objects. For example, you
      cannot define a class in an External Method script file and
      assign it as an attribute of a Zope object. This causes problems
      with Zope's persistence machinery. You also cannot easily hand
      instances of your own classes over to DTML or scripts. The issue
      here is that your instances won't have Zope security
      information. You can define and use your own classes and
      instances to your heart's delight, just don't expect Zope to use
      them directly. Limit yourself to returning simple Python
      structures like strings, dictionaries and lists or Zope objects.</p><h2>  Using Perl-based Scripts</h2>
<p>    Perl-based Scripts allow you to script Zope in Perl. If you love Perl
    and don't want to learn Python to use Zope, these scripts are for
    you. Using Perl-based Scripts you can use all your favorite Perl modules
    and treat Zope like a collection of Perl objects.</p><h3>    The Perl Language</h3>
<p>      <a href="http://www.perl.com/">Perl</a> is a high-level scripting language like
      Python.  From a broad perspective, Perl and Python are very similar
      languages, they have similar primitive data constructs and employ
      similar programming constructs.</p><p>      Perl is a popular language for Internet scripting. In the early
      days of CGI scripting, Perl and CGI were practically
      synonymous. Perl continues to be the dominant Internet scripting
      language.</p><p>      Perl has a very rich collection of modules for tackling almost
      any computing task.  <a href="http://search.cpan.org/">CPAN</a>
      (Comprehensive Perl Archive Network) is the authoritative guide
      to Perl resources.</p><p>      Perl-based Zope scripts are available for download from
      <a href="http://downloads.activestate.com/Zope-Perl/">ActiveState</a>.
      Perl-based scripts require you to have Perl installed, and a few
      other packages, and how to install these things is beyond the
      scope of this book.  See the documentation that comes with
      Perl-based scripts from the above URL. There is also more
      information provided by Andy McKay available on
      <a href="http://www.zope.org/Members/andym/wiki/FrontPage">Zope.org</a>.</p><h3>    Creating Perl-based Scripts</h3>
<p>      Perl-based Scripts are quite similar to Python-based Scripts. Both have
      access to Zope objects and are called in similar ways. Here's
      the Perl hello world program:<pre>        my $name=shift;
        return &quot;Hello $name.&quot;;</pre>
</p><p>      Let's take a look at a more complex example script by Monty
      Taylor. It uses the <code>LWP::UserAgent</code> package to retrieve the URL
      of the daily Dilbert comic from the network. Create a Perl-based
      Script named <em>get_dilbert_url</em> with this code:<pre>        use LWP::UserAgent;

        my $ua = LWP::UserAgent-&gt;new;

        # retrieve the Dilbert page
        my $request = HTTP::Request-&gt;new('GET','http://www.dilbert.com');
        my $response = $ua-&gt;request($request);

        # look for the image URL in the HTML
        my $content = $response-&gt;content;
        $content =~ m,(/comics/dilbert/archive/images/[^&quot;]*),s;

        # return the URL
        return $content        </pre>
</p><p>      You can display the daily Dilbert comic by calling this script
      from DTML by calling the script inside an HTML <em>IMG</em> tag:<pre>        &lt;img src=&quot;&amp;dtml-get_dilbert_url;&quot;&gt;</pre>
</p><p>      However there is a problem with this code. Each time you display
      the cartoon, Zope has to make a network connection. This is
      inefficient and wasteful. You'd do much better to only figure
      out the Dilbert URL once a day.</p><p>      Here's a script <em>cached_dilbert_url</em> that improves the
      situation by keeping track of when it last fetched the Dilbert
      URL with a <em>dilbert_url_date</em> property:<pre>        my $context=shift;
        my $date=$context-&gt;getProperty('dilbert_url_date');

        if ($date==null or $now-$date &gt; 1){
            my $url=$context-&gt;get_dilbert_url();
            $context-&gt;manage_changeProperties(
              dilbert_url =&gt; $url
              dilbert_url_time =&gt; $now
            );
        }
        return $context-&gt;getProperty('dilbert_url');</pre>
</p><p>      This script uses two properties, <em>dilbert_url</em> and
      <em>dilbert_url_date</em>. If the URL gets too old, a new one is
      fetched. You can use this script from DTML just like the original
      script:<pre>        &lt;img src=&quot;&amp;dtml-cached_dilbert_url;&quot;&gt;</pre>
</p><p>      You can use Perl and DTML together to control your logic and
      your presentation.</p><h3>    Perl-based Script Security</h3>
<p>      Like DTML and Python-based Scripts, Perl-based Scripts constrain
      you in the Zope security system from doing anything that you are
      not allowed to do.  Script security is similar in both languages,
      but there are some Perl specific constraints.</p><p>      First, the security system does not allow you to <em>eval</em> an
      expression in Perl.  For example, consider this script:<pre>        my $context = shift;
        my $input = shift;

        eval $input</pre>
</p><p>      This code takes an argument and evaluates it in Perl.  This means you
      could call this script from, say an HTML form, and evaluate the
      contents of one of the form elements.  This is not allowed since the
      form element could contain malicious code.</p><p>      Perl-based Scripts also cannot assign new variables to any object other
      than local variables that you declare with <em>my</em>.</p><h2>  DTML versus Python versus Perl</h2>
<p>    Zope gives you many ways to script. For small scripting tasks the
    choice of Python, Perl or DTML probably doesn't make a big
    difference. For larger, logic-oriented tasks you should use Python or
    Perl. You should choose the language you are most comfortable with. Of
    course, your boss may want to have some say in the matter too.</p><p>    Just for comparison sake here is a simple script suggested by
    Gisle Aas, the author of Perl-based Scripts,
    in three different languages.</p><p>    In DTML:<pre>      &lt;dtml-in objectValues&gt;
        &lt;dtml-var getId&gt;: &lt;dtml-var sequence-item&gt;
      &lt;/dtml-in&gt;
      done</pre>
</p><p>    In Python:<pre>      for item in context.objectValues():
          print &quot;%s: %s&quot; % (item.getId(), item)
      print &quot;done&quot;
      return printed</pre>
</p><p>    In Perl:<pre>      my $context = shift;
      my @res;

      for ($context-&gt;objectValues()) {
          push(@res, join(&quot;: &quot;, $_-&gt;getId(), $_));
      }
      join(&quot;\n&quot;, @res, &quot;done&quot;);</pre>
</p><p>    Despite the fact that Zope is implemented in Python, it
    follows the Perl philosophy that there's more than one way to
    do it.</p><h2>  Remote Scripting and Network Services</h2>
<p>    Web servers are used to serve content to software clients; usually
    people using web browser software.  The software client can also be
    another computer that is using your web server to access some kind of
    service.</p><p>    Because Zope exposes objects and scripts on the web, it can be used to
    provide a powerful, well organized, secure web API to other remote
    network application clients.</p><p>    There are two common ways to remotely script Zope.  The first way
    is using a simple remote procedure call protocol called
    <em>XML-RPC</em>.  XML-RPC is used to execute a procedure on a remote
    machine and get a result on the local machine.  XML-RPC is designed
    to be language neutral, and in this chapter you'll see examples in
    Python, Perl and Java.</p><p>    The second common way to remotely script Zope is with any HTTP
    client that can be automated with a script.  Many language
    libraries come with simple scriptable HTTP clients and there are
    many programs that let you you script HTTP from the command line.</p><h3>    Using XML-RPC</h3>
<p>      XML-RPC is a simple remote procedure call mechanism that works
      over HTTP and uses XML to encode information. XML-RPC clients
      have been implemented for many languages including Python, Perl,
      Java, JavaScript, and TCL.</p><p>      In-depth information on XML-RPC can be found at the <a href="http://www.xmlrpc.org/">XML-RPC
      website</a>. </p><p>      All Zope scripts that can be called from URLs can be called via
      XML-RPC. Basically XML-RPC provides a system to marshal
      arguments to scripts that can be called from the web. As you saw
      earlier in the chapter Zope provides its own marshaling
      controls that you can use from HTTP. XML-RPC and Zope's own
      marshaling accomplish much the same thing. The advantage of
      XML-RPC marshaling is that it is a reasonably supported
      standard that also supports marshaling of return values as well
      as argument values.</p><p>      Here's a fanciful example that shows you how to remotely script
      a mass firing of janitors using XML-RPC.</p><p>      Here's the code in Python:<pre>        import xmlrpclib

        server = xmlrpclib.Server('http://www.zopezoo.org/')
        for employeeID in server.JanitorialDepartment.personnel():
            server.fireEmployee(employee)</pre>
</p><p>      In Perl:<pre>        use Frontier::Client;

        $server = Frontier::Client-&gt;new(url =&gt; &quot;http://www.zopezoo.org/&quot;);

        $employees = $server-&gt;call(&quot;JanitorialDepartment.personnel&quot;);
        foreach $employee ( @$employees ) {

          $server-&gt;call(&quot;fireEmployee&quot;,$server-&gt;string($employee));

        }</pre>
</p><p>      In Java:<pre>        try {
            XmlRpcClient server = new XmlRpcClient(&quot;http://www.zopezoo.org/&quot;);
            Vector employees = (Vector) server.execute(&quot;JanitorialDepartment.personnel&quot;);

            int num = employees.size();
            for (int i = 0; i &lt; num; i++) {
                Vector args = new Vector(employees.subList(i, i+1));
                server.execute(&quot;fireEmployee&quot;, args);
            }

        } catch (XmlRpcException ex) {
            ex.printStackTrace();
        } catch (IOException ioex) {
            ex.printStackTrace();
        }</pre>
</p><p>      Actually the above example will probably not run correctly, since you
      will most likely want to protect the <em>fireEmployee</em> script. This brings
      up the issue of security with XML-RPC. XML-RPC does not have any
      security provisions of its own; however, since it runs over HTTP it can
      leverage existing HTTP security controls. In fact Zope treats an
      XML-RPC request exactly like a normal HTTP request with respect to
      security controls. This means that you must provide authentication in
      your XML-RPC request for Zope to grant you access to protected
      scripts. The Python client at the time of this writing does not
      support control of HTTP Authorization headers. However it is a fairly
      trivial addition. For example, an article on XML.com <a href="http://www.xml.com/pub/2000/01/xmlrpc/index.html">Internet
      Scripting: Zope and
      XML-RPC</a> includes a
      patch to Python's XML-RPC support showing how to add HTTP authorization
      headers to your XML-RPC client.</p><h3>    Remote Scripting with HTTP</h3>
<p>      Any HTTP client can be used for remotely scripting Zope.</p><p>      On Unix systems you have a number of tools at your disposal for
      remotely scripting Zope. One simple example is to use <em>wget</em> to call
      Zope script URLs and use <em>cron</em> to schedule the script calls. For
      example, suppose you have a Zope script that feeds the lions and you'd
      like to call it every morning.  You can use <em>wget</em> to call the script
      like so:<pre>        $ wget --spider http://www.zopezope.org/Lions/feed</pre>
</p><p>      The <em>spider</em> option tells <em>wget</em> not to save the response as a
      file. Suppose that your script is protected and requires
      authorization. You can pass your user name and password with <em>wget</em> to
      access protected scripts:<pre>        $ wget --spider --http_user=ZooKeeper --http_pass=SecretPhrase http://www.zopezope.org/Lions/feed</pre>
</p><p>      Now let's use <em>cron</em> to call this command every morning at 8am. Edit
      your crontab file with the <em>crontab</em> command:<pre>        $ crontab -e</pre>
</p><p>      Then add a line to call wget every day at 8 am:<pre>        0 8 * * * wget -v --spider --http_user=ZooKeeper --http_pass=SecretPhrase http://www.zopezoo.org/Lions/feed</pre>
</p><p>      The only difference between using <em>cron</em> and calling <em>wget</em> manually is
      that you should use the <em>v</em> switch when using <em>cron</em> since you don't
      care about output of the <em>wget</em> command.</p><p>      For our final example let's get really perverse. Since networking is
      built into so many different systems, it's easy to find an unlikely
      candidate to script Zope. If you had an Internet-enabled toaster you
      would probably be able to script Zope with it. Let's take Microsoft
      Word as our example Zope client. All that's necessary is to get Word to
      agree to tickle a URL.</p><p>      The easiest way to script Zope with Word is to tell word to open a
      document and then type a Zope script URL as the file name as shown in
      <a href="#8-9">Figure 8-9</a>.</p><p>      <a name="8-9"></a>
<img src="Figures/8-9.png" alt="Calling a URL with Microsoft Word">
<p><b>Figure 8-9</b> Calling a URL with Microsoft Word</p>
</p><p>      Word will then load the URL and return the results of calling the Zope
      script. Despite the fact that Word doesn't let you POST arguments this
      way, you can pass GET arguments by entering them as part of the URL.</p><p>      You can even control this behavior using Word's built-in Visual Basic
      scripting. For example, here's a fragment of Visual Basic that tells
      Word to open a new document using a Zope script URL:<pre>        Documents.Open FileName:=&quot;http://www.zopezoo.org/LionCages/wash?use_soap=1&amp;water_temp=hot&quot; </pre>
</p><p>      You could use Visual Basic to call Zope script URLs in many different
      ways.</p><p>      Zope's URL to script call translation is the key to remote
      scripting. Since you can control Zope so easily with simple URLs you
      can easy script Zope with almost any network-aware system.</p><h2>  Conclusion</h2>
<p>    Zope provides scripting with Python and Perl. With scripts you can
    control Zope objects and glue together your application's logic,
    data, and presentation. You can also perform serious programming
    tasks such as image processing and XML parsing.</p><p>    In the next chapter you'll learn about ZCatalog, Zope's built-in
    search engine. </p></body>
</html>