Author: tziade Date: Thu Mar 16 22:34:19 2006 New Revision: 2650 Modified: cookbook/trunk/recipe57.en.tex Log: gone further in recipe 57 (en)
Modified: cookbook/trunk/recipe57.en.tex ============================================================================== --- cookbook/trunk/recipe57.en.tex (original) +++ cookbook/trunk/recipe57.en.tex Thu Mar 16 22:34:19 2006 @@ -9,7 +9,7 @@ \chapter*{Organizing the code in a package} \begin{problem} -A packages is composed of Python files and ZCML ones. It may also contains +A package is composed of Python files and ZCML ones. It may also contains page templates and resources, like images, css or javascript files. This recipe explains how to organize all these elements, for the package @@ -26,9 +26,9 @@ \code{\_\_init\_\_.py} file in order to be recognized by the interpreter. In Zope, the package has an extended meaning: it's the smallest pluggable -part of the server. This was called \textit{Product} under Zope 2. Since the +part of the server. This was known as \textit{Product} under Zope 2. Since the initialization mechanism is based on the ZCML files, Zope acts the same way -as Python, but looks for a \code{configure.zcml} in the root folder. +as Python, but looks for a \code{configure.zcml} in the package folder. From there, any kind of code organization would work, as long as the modules are linked in the ZCML file. But a bit of structuration is needed, based on a @@ -46,27 +46,131 @@ \end{verbatim} These two files are gathered in a folder, which is declared in a configuration -file in the {INSTANCE/etc/package-includes} folder. The configuration file +file in the \code{INSTANCE/etc/package-includes} folder. The configuration file has to be named with a \textit{-configure.zcml} suffix, and conventionally uses -the package folder name for the prefix. So a package called \textit{dummy} -will have a \textit{dummy-configure.zcml} file, declaring it. +the package folder name to complete its name. So a package called +\textit{dummypackage} will have a \textit{dummypackage-configure.zcml} file, +declaring it. -\codetitle{The dummy-configure.zcml file:} +\codetitle{The dummypackage-configure.zcml file:} \begin{verbatim} -<include package="dummy"/> +<include package="dummypackage"/> \end{verbatim} -At startup, Zope will include the first \code{dummy} folder it finds in the -path. +At startup, Zope will include the first \code{dummypackage} folder it finds +in the path. The best place to store packages is \code{INSTANCE/lib/python}. \section*{Setting up a real-world structure} -The \code{dummy} package will now implement an exciting and original feature: -an \code{hello.html} browser view that displays "Hello, world" on the browser. +The \code{dummy} package will now implement a simple feature to demonstrate +a typical structure: a new kind of object that keep a track of the number +of times it has been accessed through a view. The package therefore needs: + +\begin{itemize} +\item An interface declaration; +\item a view class; +\item a content class. +\end{itemize} + +\subsection*{Interface declaration} + +All interface declarations in the package are gathered in a unique file called +\code{interfaces.py}. This approach provides: + +\begin{itemize} +\item A conventional way to refer to the interfaces. For instance, +a \code{for} attribute in a \code{browser:view} directive will +always be of the form: \textit{package.interfaces.IMyInterface}. +\item A helper to organise subclassing between interfaces. +\end{itemize} -***The first nedeed element is a \code{IHelloWorld}�interface************ -interfaces*** -browser***** +The interface will be called \code{IDummyCounter} and added into +\code{interfaces.py}. + +\codetitle{interfaces.py:} +\begin{verbatim} +from zope.interface import Interface + +class IDummyCounter(Interface): + + def count(): + """ returns the counter value + after it has been inc'ed """ + +\end{verbatim} + +\subsection*{Content class} + +The content class is taking care of the counter, and derives from +a basic persistent class, so it can be stored in the ZODB. This +class is saved in a file that has the same name. + +\codetitle{dummycounter.py:} +\begin{verbatim} +import persistent +from zope.interface import implements +from interfaces import IDummyCounter + +class DummyCounter(persistent.Persistent): + + implements(IDummyCounter) + + def __init__(self, initial=0): + self._count = initial + + def count(self): + self._count += 1 + return self._count + +\end{verbatim} + +At this level, \code{DummyCounter} is fully functionnal. + +\codetitle{Testing the class in a prompt:} +\begin{verbatim} +>>> from dummypackage.dummycounter import DummyCounter +>>> my_counter = DummyCounter() +>>> my_counter.count() +1 +>>> my_counter.count() +2 +>>> my_counter.count() +3 +\end{verbatim} + +\subsection*{View class} + +The code that is in charge of displaying the \code{DummyCounter} instances, +is a simple class, derived from \code{BrowserView}, and called +\code{DummyCounterView}. Since views are declared to Zope with directives +provided by the \code{browser} directives, they are gather in a file +called \code{browser.py}. + +\codetitle{browser.py:} +\begin{verbatim} +from zope.app.publisher.browser import BrowserView + +class DummyCounterView(BrowserView): + + def getCount(self): + return 'I have been seen %d times' % self.context.count() +\end{verbatim} + +\code{getCount} provides the code specific to the publishing process. + +\codetitle{Trying out the view:} +\begin{verbatim} +>>> from dummypackage.dummycounter import DummyCounter +>>> my_counter = DummyCounter() +>>> from dummypackage.browser import DummyCounterView +>>> my_counter_view = DummyCounterView(my_counter, None) +>>> my_counter_view.getCount() +'I have been seen 1 times' +>>> my_counter_view.getCount() +'I have been seen 2 times' +>>> my_counter_view.getCount() +'I have been seen 3 times' +\end{verbatim} \subsection*{Choosing between module-based or folder-based} -- http://lists.nuxeo.com/mailman/listinfo/z3lab-checkins