Python With ZK Part2

From Documentation
Python With ZK Part2

Author
BobZK
Date
May 11, 2010
Version
ZK 3.6+

Python With ZK Part2

In a previous smalltalk Python With ZK I showed how to separate Python code from ZUL code together with a little MVC and "design by convention".

This smalltalk builds on the previous one and adds model code to access a Postgresql database.

Correction to PythonForwardComposer

There were two minor mistakes in the previous smalltalk in the code for PythonForwardComposer. Below is the corrected code -

Python composer - PythonForwardComposer.py

from org.zkoss.zk.ui.util import GenericForwardComposer                                   # removed ";" from end of line
class PythonForwardComposer(GenericForwardComposer):
    
    def __init__(self):
        GenericForwardComposer.__init__(self, "_")
    
    def doAfterCompose(self, component):
        GenericForwardComposer.doAfterCompose(self, component)
        for method in dir(self):
            if callable(getattr(self, method)):
                if method.startswith("on") and '_' in method:                         # allow names that already have an underscore
                    idx = method.index('_')
                    comp = component.getFellowIfAny(method[idx + 1:])
                    if comp != None:
                        comp.addForward(method[:idx], component, method)
                        component.addEventListener(method, self)

    def onEvent(self, evt):
        if callable(getattr(self, evt.name)):
            getattr(self, evt.name)(evt)

Both changes from the original are commented.

findairport.zul

A fairly simple bit of ZUL (with the appropriate includes) that allows the user to enter an airport code and the airport name is returned.

<?page title="Find Airports (Model/View/Contoller)" contentType="text/html;charset=UTF-8"?>
<zk>

<zscript src="PythonForwardComposer.py" language="python"></zscript>
<zscript src="BobModel.py" language="python"></zscript>
<zscript src="BobController.py" language="python"></zscript>

	<window id="mywin" title="Find Airports" border="normal" height="200px"
		apply="${BobController}" width="200px" closable="true"
		sizable="true">
		Find an Airport:
		<label id="lMessage"/>
		<textbox id="txMessage"/>
		<button id="okButton" label="Go Find It"/>
	</window>
</zk>

Notice again the use of 'apply="${BobController}"' on the window. Also the ID's on the various ZUL components.

The screen should look like -

Screenshot-4.png

BobController.py

Now for our controller code.

from org.zkoss.zk.ui.event import Events

class BobController(PythonForwardComposer):
    
    def __init__(self):
        self.model = BobModel()

    def onClick_okButton(self, event):
        message = txMessage.getValue().upper()
        lMessage.setValue(self.model.getAirport(message))
                  
    def onClose_mywin(self, event):
        Events.getRealOrigin(event).stopPropagation()
        return False

Notice how we instantiate the model code (BobModel) in the __init__ method.

The onClick method gets control when the OK button is clicked, it gets the entered airport code and then invokes the getAirport method in BobModel, passing to it the airport code and getting the airport name in return. The airport name is placed directly into the ZUL variable "lMessage".

The onClose method is there to show you how any ZUL component can have a method in BobController. In this case it simply ignores the close event.

Notice again how we simply use "txMessage" and "lMessage" as defined in the ZUL without complicated access code. The auto-wiring of PythonForwardComposer and "design by convention" make access to these variables simple.

BobModel.py

Now we need our model code -

from com.ziclix.python.sql import zxJDBC

class Select:
    __STATIC  = 0
    __DYNAMIC = 1
    __jndiName = "java:comp/env/jdbc/mydatabase"
    __factory = "com.sun.jndi.fscontext.RefFSContextFactory"
    
    def __init__(self, select):
        self.select = select
        
    def runSelect(self, variables):
        try:    
            conn = None
            cur = None   
            result = []                     # a list
            result_dict = {}                # a dict
            
            conn = zxJDBC.lookup(Select.__jndiName, INITIAL_CONTEXT_FACTORY=Select.__factory)
            cur = conn.cursor(Select.__DYNAMIC)
            cur.execute(self.select, variables)
            
            rows = cur.fetchall()
            if cur.rowcount == 0:
                return None

            for row in rows:
                result_dict = {}
                for i, column in enumerate(row):
                    result_dict[cur.description[i][0]] = column
                result.append(result_dict)
            return tuple(result)
        
        finally:
            if cur is not None:
                cur.close()
                cur = None
            if conn is not None:
                conn.close()
                conn = None 
    
class BobModel:
   
    def getAirport(self, airport):
        a = Select("select a_name from f_airports where a_code = ?;")
        rowset = a.runSelect([airport])
        if rowset is None:
            return "Not Found"
        else:
            return rowset[0]['a_name']

You can see in class BobModel in method getAirport that we first instantiate a select object ("a") and then run that select with the airport code as the variable.

I am not going to say much more about this code, I'm sure other people would code it differently, but it does work It uses Postgresql (but can be changed easily to any other DB) and zxJDBC with tomcat connection pooling. By coding it in such a manner, we ensure that we never hold a connection longer than absolutely necessary and that we always release the connection.

The appropriate context code needs entering into context.xml file in the META-INF directory (WebContent/META-INF/context.xml) -

context.xml

<?xml version="1.0" encoding="UTF-8"?>
<Context docBase="TEST" path="/TEST" debug="5" reloadable="true" crossContext="true">
<Resource name="jdbc/mydatabase" username="myname" password="mypassword"
   url="jdbc:postgresql://localhost/mydatabase"
   auth="Container" defaultAutoCommit="false"
   driverClassName="org.postgresql.Driver" maxActive="20"
   type="javax.sql.DataSource" />
</Context>

The code in context.xml is the only code you need to change to use the same zul, controller and model code with a different database server (eg. mysql).

Of course you will need to set up your own database (mydatabase) and f_airports table with an a_code column and an a_name column, if you really want to test the code.

Conclusion

it is possible to use ZK with Python in a Model-View-Controller fashion and keep the code separate from each other. You can easily set this up in Eclipse and use all the highlighting and code completion assistance of Eclipse, ZK and Pydev.

The only concerns with this approach are the overhead of using Python and the security implications of having code in the WebContent directory. However with a bit of effort it is possible to overcome the security problem (more of this in a later smalltalk perhaps). The performance penalty really is not too bad. In most cases it can be ignored.

See Also

Comments



Copyright © BobZK. This article is licensed under GNU Free Documentation License.