diff options
| author | Paul Everitt <paul@agendaless.com> | 2008-07-24 17:14:16 +0000 |
|---|---|---|
| committer | Paul Everitt <paul@agendaless.com> | 2008-07-24 17:14:16 +0000 |
| commit | 1a7b14de4bc89ef0fd162ef8e62d01926e42b54e (patch) | |
| tree | 8d9ccc8187ac29eb80ac310c6f82c17d548cdf54 /docs/tutorials | |
| parent | c1a6ef6f751a16b7a352bc3fdad13d4b5b73f212 (diff) | |
| download | pyramid-1a7b14de4bc89ef0fd162ef8e62d01926e42b54e.tar.gz pyramid-1a7b14de4bc89ef0fd162ef8e62d01926e42b54e.tar.bz2 pyramid-1a7b14de4bc89ef0fd162ef8e62d01926e42b54e.zip | |
Add tutorial sections
Diffstat (limited to 'docs/tutorials')
39 files changed, 1728 insertions, 0 deletions
diff --git a/docs/tutorials/cluegun/index.rst b/docs/tutorials/cluegun/index.rst new file mode 100644 index 000000000..4b8cacf52 --- /dev/null +++ b/docs/tutorials/cluegun/index.rst @@ -0,0 +1,21 @@ +====================================== + Shared Clipboard with repoze.cluegun +====================================== + +repoze.cluegun is a "pastebin" application, showing how you can use +the ``repoze.bfg`` framework for posting and managing paste sessions. + +In this tutorial, you'll see: + + - Persistence + + - Handling form data + + - z3c.pt templating + + - Security + +.. toctree:: + :maxdepth: 2 + + diff --git a/docs/tutorials/index.rst b/docs/tutorials/index.rst new file mode 100644 index 000000000..0f3d798bb --- /dev/null +++ b/docs/tutorials/index.rst @@ -0,0 +1,9 @@ +============================== +``repoze.bfg`` Tutorials +============================== + +.. toctree:: + :maxdepth: 2 + + lxmlgraph + diff --git a/docs/tutorials/lxmlgraph/NOTES.txt b/docs/tutorials/lxmlgraph/NOTES.txt new file mode 100644 index 000000000..60b4b9f55 --- /dev/null +++ b/docs/tutorials/lxmlgraph/NOTES.txt @@ -0,0 +1,6 @@ + +To Do +------------ + +- Dream up a ZPT/XSLT theme engine + diff --git a/docs/tutorials/lxmlgraph/background.rst b/docs/tutorials/lxmlgraph/background.rst new file mode 100644 index 000000000..975c23a1d --- /dev/null +++ b/docs/tutorials/lxmlgraph/background.rst @@ -0,0 +1,113 @@ +Background +==================== + +This demo application presumes that you have an interest in XML +technologies and might want to leverage them for a fast-as-hell, but +rich and dynamic, website. In this demo application, we build up, +bit-by-bit, the functionality. Thus, you don't have to know squatola +about XML to follow along. + +In fact, the real purpose of this demo app is to teach its author how +to use the stack (repoze.bfg, Paster, eggs, etc.) + +In summary: + + - Represent a hierarchical site as hierarchical XML + + - Inject ``repoze.bfg`` semantics into elements using ``lxml`` + + - Support flexible-but-fast rendering with XSLT + +.. warning:: + + If you dislike XML and related technologies such as XPath and XSLT, + you'll thoroughly detest this sample application. Just to be + stupendously clear, ``repoze.bfg`` is in no way dependent on XML. + On the other hand, ``repoze.bfg`` happens to make XML publishing + kind fun. + +What It Does +------------------- + +Imagine you have a website that looks like this:: + + / + folder1/ + doc1 + doc2 + image1 + folder2/ + doc2 + +Meaning, a heterogenous, nested folder structure, just like your hard +drive. (Unless you're one of those folks that uses your Windows +Desktop as a flat filing system.) How might I get that information +into a website? + +Using ``repoze.bfg``, of course. More specifically, with an XML file +that models that hierarchy: + +.. literalinclude:: step00/simplemodel.xml + :language: xml + +How It Works +------------------- + +To coerce ``repoze.bfg`` into publishing this model, I just need to +sprinkle in some Python behavior. For example, ``repoze.bfg`` uses +``__getitem__`` to traverse the model. I need my XML data to support +this method. Moreover, I want some specific behavior: run an XPath +express on the node to get the child with the ``@name`` attribute +matching the URL hop. + +Fortunately ``lxml`` makes this easy. I can inject my nodes with a +class that I write, thus providing my own ``__getitem__`` behavior. + +That class can also assert that my XML nodes provide an interface. +The interface then lets me glue back into the standard ``repoze.bfg`` +machinery, such as associating views and permissions into the model. + +Neato torpedo. And stinking fast. + +Next up, I need to provide views for the elements in the model. I +could, for example, use ZPT and manipulate the XML data using Python +expressions against the lxml API. Or, I could use XSLT. + +For the latter, I could register a different XSLT for every "view" on +every interface. Or, I could write one big XSLT, and let its template +matching machinery decide who to render in a certain context. + +And finally, I could pass in just a single node and render it, or pass +in the entire tree with a parameter identifying the context node. + +In the course of this writeup, we'll build ``repoze.lxmlgraph`` +step-by-step, starting with no XML. Each of those decisions will be +analyzed an implemented. At the end, you'll see both the resulting +demo application, plus the thought process that went along with it. + +What It Might Do +-------------------- + +This demo application has the potential to show some other interesting +investigations: + +#. **Authorization**. By hooking up support for an ``__acl__`` + property, I can store ACL information on a single node, on an + ancestor, on the ``<site>`` root, on the Python class, or any + combination thereof. Additionally, I can wire up the + ``__parent__`` attribute as a property that makes an lxml + ``node.getparent()`` call. + +#. **Multiple views**. Instead of just having a single default view + on a node, I can allow other view names, all pointing at the same + factory and XSLT. I simple grab that name and pass it in as a + paramter to the XSLT, which will run a different rule for + rendering. + +#. **Forms**. To edit data in the model, I need to render a form, + then handle post data on the way back in. For the former, it's + *really* easy in XSLT to make a very powerful, flexible, and + extensisible form rendering system. For the latter, I'll have to + learn more about POST handlers in ``repoze.bfg``. + + diff --git a/docs/tutorials/lxmlgraph/index.rst b/docs/tutorials/lxmlgraph/index.rst new file mode 100644 index 000000000..42c8ec503 --- /dev/null +++ b/docs/tutorials/lxmlgraph/index.rst @@ -0,0 +1,21 @@ +Publishing XML with repoze.bfg +========================================== + +``repoze.bfg`` is, well, fun. It brings back the good old days of +Bobo, but with the good new days of WSGI. With ``repoze.bfg``, +hierarchical websites are a cinch to develop. + +XML is also hierarchical. ``repoze.lxmlgraph`` is a demo application +for ``repoze.bfg`` that shows publishing a tree of XML as a +hierarchical website, while leveraging the facilities the gun gives +you. + +.. toctree:: + :maxdepth: 2 + + background + installation + step01 + step02 + step03 + step04 diff --git a/docs/tutorials/lxmlgraph/installation.rst b/docs/tutorials/lxmlgraph/installation.rst new file mode 100644 index 000000000..b30c058b8 --- /dev/null +++ b/docs/tutorials/lxmlgraph/installation.rst @@ -0,0 +1,22 @@ +Installation +===================== + +You can get the final form of the demo application, along with all the +steps along the way, via the miracles of virtualenv, easy_install and Paster +templates:: + + $ virtualenv --no-site-packages myapp + $ cd myapp + $ source bin/activate + $ easy_install repoze.lxmlgraph + $ paster create -t lxmlgraph_project + +Answer the questions, then run the demo: + + cd <<projectname>> + python run.py + +At that point a URL such as ``http://localhost:5432/folder2/query1`` +should work. + +
\ No newline at end of file diff --git a/docs/tutorials/lxmlgraph/oxygen.xpr b/docs/tutorials/lxmlgraph/oxygen.xpr new file mode 100644 index 000000000..6b42af9e1 --- /dev/null +++ b/docs/tutorials/lxmlgraph/oxygen.xpr @@ -0,0 +1,509 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project> + <meta> + <filters directoryPatterns="" filePatterns="" + positiveFilePatterns="" showHiddenFiles="false"/> + <options> + <serialized> + <map> + <entry> + <String xml:space="preserve">scenario.associations</String> + <scenarioAssociation-array> + <scenarioAssociation> + <field name="name"> + <String xml:space="preserve">xsltview</String> + </field> + <field name="type"> + <String xml:space="preserve">XML</String> + </field> + <field name="url"> + <String xml:space="preserve">step04/myapp/xsltview.xsl</String> + </field> + </scenarioAssociation> + <scenarioAssociation> + <field name="name"> + <String xml:space="preserve">debugui-entryviewer</String> + </field> + <field name="type"> + <String xml:space="preserve">XML</String> + </field> + <field name="url"> + <String xml:space="preserve">../../../repoze.debug/trunk/repoze/debug/static/debugui-entryviewer.xsl</String> + </field> + </scenarioAssociation> + <scenarioAssociation> + <field name="name"> + <String xml:space="preserve">Docbook PDF modified</String> + </field> + <field name="type"> + <String xml:space="preserve">XSL</String> + </field> + <field name="url"> + <String xml:space="preserve">../../../../playground/paul/psu2008/themes/Untitled5.xml</String> + </field> + </scenarioAssociation> + <scenarioAssociation> + <field name="name"> + <String xml:space="preserve">docbook/docbook5.framework/DocBook 5/Docbook HTML</String> + </field> + <field name="type"> + <String xml:space="preserve">XSL</String> + </field> + <field name="url"> + <String xml:space="preserve">../../../../../guidetoec2.xml</String> + </field> + </scenarioAssociation> + </scenarioAssociation-array> + </entry> + <entry> + <String xml:space="preserve">scenarios</String> + <scenario-array> + <scenario> + <field name="name"> + <String xml:space="preserve">Execute DDXQuery</String> + </field> + <field name="baseURL"> + <String xml:space="preserve"></String> + </field> + <field name="footerURL"> + <String xml:space="preserve"></String> + </field> + <field name="fOPMethod"> + <null/> + </field> + <field name="fOProcessorName"> + <null/> + </field> + <field name="headerURL"> + <String xml:space="preserve"></String> + </field> + <field name="inputXSLURL"> + <String xml:space="preserve">${currentFileURL}</String> + </field> + <field name="inputXMLURL"> + <String xml:space="preserve"></String> + </field> + <field name="defaultScenario"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="isFOPPerforming"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="type"> + <String xml:space="preserve">DD_XQUERY</String> + </field> + <field name="saveAs"> + <Boolean xml:space="preserve">true</Boolean> + </field> + <field name="openInBrowser"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="outputFile"> + <null/> + </field> + <field name="openOtherLocationInBrowser"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="locationToOpenInBrowserURL"> + <String xml:space="preserve"></String> + </field> + <field name="openInEditor"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="showInHTMLPane"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="showInXMLPane"> + <Boolean xml:space="preserve">true</Boolean> + </field> + <field name="showInSVGPane"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="useXSLTInput"> + <Boolean xml:space="preserve">true</Boolean> + </field> + <field name="xsltParams"> + <list/> + </field> + <field name="cascadingStylesheets"> + <String-array/> + </field> + <field name="xslTransformer"> + <String xml:space="preserve">DataDirect</String> + </field> + <field name="extensionURLs"> + <String-array/> + </field> + </scenario> + <scenario> + <field name="name"> + <String xml:space="preserve">Execute SQL</String> + </field> + <field name="baseURL"> + <String xml:space="preserve"></String> + </field> + <field name="footerURL"> + <String xml:space="preserve"></String> + </field> + <field name="fOPMethod"> + <null/> + </field> + <field name="fOProcessorName"> + <null/> + </field> + <field name="headerURL"> + <String xml:space="preserve"></String> + </field> + <field name="inputXSLURL"> + <String xml:space="preserve">${currentFileURL}</String> + </field> + <field name="inputXMLURL"> + <String xml:space="preserve"></String> + </field> + <field name="defaultScenario"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="isFOPPerforming"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="type"> + <String xml:space="preserve">SQL</String> + </field> + <field name="saveAs"> + <Boolean xml:space="preserve">true</Boolean> + </field> + <field name="openInBrowser"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="outputFile"> + <null/> + </field> + <field name="openOtherLocationInBrowser"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="locationToOpenInBrowserURL"> + <String xml:space="preserve"></String> + </field> + <field name="openInEditor"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="showInHTMLPane"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="showInXMLPane"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="showInSVGPane"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="useXSLTInput"> + <Boolean xml:space="preserve">true</Boolean> + </field> + <field name="xsltParams"> + <list/> + </field> + <field name="cascadingStylesheets"> + <String-array/> + </field> + <field name="xslTransformer"> + <String xml:space="preserve">JDBC</String> + </field> + <field name="extensionURLs"> + <String-array/> + </field> + </scenario> + <scenario> + <field name="name"> + <String xml:space="preserve">Execute XQuery</String> + </field> + <field name="baseURL"> + <String xml:space="preserve"></String> + </field> + <field name="footerURL"> + <String xml:space="preserve"></String> + </field> + <field name="fOPMethod"> + <null/> + </field> + <field name="fOProcessorName"> + <null/> + </field> + <field name="headerURL"> + <String xml:space="preserve"></String> + </field> + <field name="inputXSLURL"> + <String xml:space="preserve">${currentFileURL}</String> + </field> + <field name="inputXMLURL"> + <String xml:space="preserve"></String> + </field> + <field name="defaultScenario"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="isFOPPerforming"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="type"> + <String xml:space="preserve">XQUERY</String> + </field> + <field name="saveAs"> + <Boolean xml:space="preserve">true</Boolean> + </field> + <field name="openInBrowser"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="outputFile"> + <null/> + </field> + <field name="openOtherLocationInBrowser"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="locationToOpenInBrowserURL"> + <String xml:space="preserve"></String> + </field> + <field name="openInEditor"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="showInHTMLPane"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="showInXMLPane"> + <Boolean xml:space="preserve">true</Boolean> + </field> + <field name="showInSVGPane"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="useXSLTInput"> + <Boolean xml:space="preserve">true</Boolean> + </field> + <field name="xsltParams"> + <list/> + </field> + <field name="cascadingStylesheets"> + <String-array/> + </field> + <field name="xslTransformer"> + <String xml:space="preserve">Saxon9B XQuery</String> + </field> + <field name="extensionURLs"> + <String-array/> + </field> + </scenario> + <scenario> + <field name="name"> + <String xml:space="preserve">Docbook PDF modified</String> + </field> + <field name="baseURL"> + <String xml:space="preserve"></String> + </field> + <field name="footerURL"> + <String xml:space="preserve"></String> + </field> + <field name="fOPMethod"> + <String xml:space="preserve">pdf</String> + </field> + <field name="fOProcessorName"> + <String xml:space="preserve">Built-in (Apache FOP)</String> + </field> + <field name="headerURL"> + <String xml:space="preserve"></String> + </field> + <field name="inputXSLURL"> + <String xml:space="preserve">${frameworks}/docbook/xsl/fo/docbook.xsl</String> + </field> + <field name="inputXMLURL"> + <String xml:space="preserve">${currentFileURL}</String> + </field> + <field name="defaultScenario"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="isFOPPerforming"> + <Boolean xml:space="preserve">true</Boolean> + </field> + <field name="type"> + <String xml:space="preserve">XSL</String> + </field> + <field name="saveAs"> + <Boolean xml:space="preserve">true</Boolean> + </field> + <field name="openInBrowser"> + <Boolean xml:space="preserve">true</Boolean> + </field> + <field name="outputFile"> + <File xml:space="preserve">${cfd}/${cfn}.pdf</File> + </field> + <field name="openOtherLocationInBrowser"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="locationToOpenInBrowserURL"> + <String xml:space="preserve"></String> + </field> + <field name="openInEditor"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="showInHTMLPane"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="showInXMLPane"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="showInSVGPane"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="useXSLTInput"> + <Boolean xml:space="preserve">true</Boolean> + </field> + <field name="xsltParams"> + <list> + <transformationParameter> + <field name="name"> + <String xml:space="preserve">draft.mode</String> + </field> + <field name="value"> + <String xml:space="preserve">no</String> + </field> + </transformationParameter> + <transformationParameter> + <field name="name"> + <String xml:space="preserve">fop.extensions</String> + </field> + <field name="value"> + <String xml:space="preserve">0</String> + </field> + </transformationParameter> + <transformationParameter> + <field name="name"> + <String xml:space="preserve">fop1.extensions</String> + </field> + <field name="value"> + <String xml:space="preserve">1</String> + </field> + </transformationParameter> + <transformationParameter> + <field name="name"> + <String xml:space="preserve">linenumbering.extension</String> + </field> + <field name="value"> + <String xml:space="preserve">1</String> + </field> + </transformationParameter> + <transformationParameter> + <field name="name"> + <String xml:space="preserve">paper.type</String> + </field> + <field name="value"> + <String xml:space="preserve">A4</String> + </field> + </transformationParameter> + <transformationParameter> + <field name="name"> + <String xml:space="preserve">use.extensions</String> + </field> + <field name="value"> + <String xml:space="preserve">1</String> + </field> + </transformationParameter> + </list> + </field> + <field name="cascadingStylesheets"> + <String-array/> + </field> + <field name="xslTransformer"> + <String xml:space="preserve">Saxon6.5.5</String> + </field> + <field name="extensionURLs"> + <String-array/> + </field> + </scenario> + <scenario> + <field name="name"> + <String xml:space="preserve">xsltview</String> + </field> + <field name="baseURL"> + <String xml:space="preserve"></String> + </field> + <field name="footerURL"> + <String xml:space="preserve"></String> + </field> + <field name="fOPMethod"> + <String xml:space="preserve">pdf</String> + </field> + <field name="fOProcessorName"> + <String xml:space="preserve">Built-in (Apache FOP)</String> + </field> + <field name="headerURL"> + <String xml:space="preserve"></String> + </field> + <field name="inputXSLURL"> + <String xml:space="preserve">${currentFileURL}</String> + </field> + <field name="inputXMLURL"> + <String xml:space="preserve">file:/Users/paul/projects/repozesvn/repoze.lxmlgraph/trunk/docs/step04/myapp/samplemodel.xml</String> + </field> + <field name="defaultScenario"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="isFOPPerforming"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="type"> + <String xml:space="preserve">XML</String> + </field> + <field name="saveAs"> + <Boolean xml:space="preserve">true</Boolean> + </field> + <field name="openInBrowser"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="outputFile"> + <null/> + </field> + <field name="openOtherLocationInBrowser"> + <Boolean xml:space="preserve">true</Boolean> + </field> + <field name="locationToOpenInBrowserURL"> + <String xml:space="preserve"></String> + </field> + <field name="openInEditor"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="showInHTMLPane"> + <Boolean xml:space="preserve">true</Boolean> + </field> + <field name="showInXMLPane"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="showInSVGPane"> + <Boolean xml:space="preserve">false</Boolean> + </field> + <field name="useXSLTInput"> + <Boolean xml:space="preserve">true</Boolean> + </field> + <field name="xsltParams"> + <list/> + </field> + <field name="cascadingStylesheets"> + <String-array/> + </field> + <field name="xslTransformer"> + <String xml:space="preserve">Xsltproc</String> + </field> + <field name="extensionURLs"> + <String-array/> + </field> + </scenario> + </scenario-array> + </entry> + <entry> + <String xml:space="preserve">scenarios.load.from.project</String> + <Boolean xml:space="preserve">true</Boolean> + </entry> + </map> + </serialized> + </options> + </meta> + <projectTree name="oxygen.xpr"> + <folder path="."/> + <folder path="../../../../../venvs/bfg/myapp-bak/"/> + </projectTree> +</project> diff --git a/docs/tutorials/lxmlgraph/step00/simplemodel.xml b/docs/tutorials/lxmlgraph/step00/simplemodel.xml new file mode 100644 index 000000000..7dbc53951 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step00/simplemodel.xml @@ -0,0 +1,26 @@ +<?xml version="1.0"?> +<site> + <title>My XMLGRAPH Website</title> + <folder xml:id="n1" name="folder1"> + <document xml:id="n11" name="doc1"> + <title>doc1 in folder1</title> + <body> + <div xmlns="http://www.w3.org/1999/xhtml"> + <p>Welcome to the site. We have lots to say.</p> + <p>Or, <em>maybe</em> not.</p> + </div> + </body> + </document> + <document xml:id="n12" name="doc2"> + <title>doc2 in folder1</title> + </document> + </folder> + <folder xml:id="n2" name="folder2"> + <document xml:id="n21" name="doc1"> + <title>doc1 in folder2</title> + </document> + <document xml:id="n22" name="doc2"> + <title>doc2 in folder2</title> + </document> + </folder> +</root> diff --git a/docs/tutorials/lxmlgraph/step01.rst b/docs/tutorials/lxmlgraph/step01.rst new file mode 100644 index 000000000..f44f48db9 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step01.rst @@ -0,0 +1,188 @@ +================================================ +Step 01: Non-XML Hello World for ``repoze.bfg`` +================================================ + +Before we work on implementing an XML graph, we need a simple starting +point for a basic ``repoze.bfg`` application. In this step we'll do +the least amount possible to run a ``repoze.bfg`` sample application. + +.. note:: + + All steps in this writeup presume that you have a virtualenv setup + as shown in the Installation step. More specifically: *make sure + you are using that virtualenv's Python* !! + +Directory Layout +==================== + +Each step in this writeup has a subdirectory for the working +application described in that step. Thus, starting at this docs +directory, we have:: + + docs/ + step01/ + myapp/ + __init__.py + configure.zcml + models.py + views.py + run.py + step02/ + step03/ + +Below we discuss each file in the ``step01``, then show how to run and +use the demo application. + + +Directory ``myapp`` +--------------------- + +This directory contains the *package* to be published. That's right, +I said *package*. ``repoze.bfg``, as we will see in a moment, uses +Python packages as the unit of publishing. + + +Module ``myapp/__init__.py`` +------------------------------ + +This is (usually-empty) file that makes a directory into a Python +"package." For ``repoze.bfg`` this is particularly important + + +Module ``myapp/configure.zcml`` +-------------------------------- + +Unlike other frameworks, ``repoze.bfg`` doesn't try to hide +configuration. Instead, configuration using ZCML is the central +wiring point. At the same time, ZCML is used in a very basic way, to +avoid confusion it can cause. + +.. literalinclude:: step01/myapp/configure.zcml + :linenos: + :language: xml + +#. Lines 1-3 provide the root node and namespaces for the + configuration language. ``bfg`` is the namespace for + ``repoze.bfg``-specific configuration directives. + +#. Line 5 initializes those ``repoze.bfg``-specific configuration + directives. + +#. Lines 8-11 register a single view for model objects that support + the IMyModel interface. In this case we made a default view, which + means going to ``/a`` triggers the default view of instance ``a``. + Finally, the ``view`` attribute points at a Python function that + does all the work for this view. + + +Module ``myapp/models.py`` +----------------------------- + +In our sample app, the ``models.py`` module provides the model data. +We create an interface ``IMyModel`` that gives us the "type system" +for our data. We then write a class ``MyModel`` that provides the +behavior for instances of the ``IMyModel`` type. + +.. literalinclude:: step01/myapp/models.py + :linenos: + +#. Lines 5-6 define the interface. + +#. Lines 8-11 provide a class for that interface. + +#. Lines 13-15 make a small tree of sample data (``/a`` and ``/b`` for + URLs.) + +#. Line 17 is a function that will be grabbed by the server when it + wants to find the top of the model. + + +Module ``myapp/views.py`` +--------------------------- + +Much of the heavy liftin in a ``repoze.bfg`` application comes in the +views. These are the bridge between the content in the model, and the +HTML given back to the browser. Since ``repoze.bfg`` is very +type-centric, URLs grab information of a certain type. Once we have +the information and its type, we can grab a view that is registered +for that type. + +.. note:: + + This "Step 01" doesn't use a template. The "view" we define is done + in Python, which generates the HTML. Most applications will use + templates to generate markup. + +.. literalinclude:: step01/myapp/models.py + :linenos: + +#. Lines 3 provide the ``my_hello_view`` that was registered as the + view. ``configure.zcml``, on Line 10, said that the default URL + for IMyModel content should run this ``my_hello_view`` function. + + The function is handed two pieces of information: the ``context`` + and the ``request``. The ``context`` is the data at the current + hop in the URL. (That data comes from the model.) The request is + an instance of a WebOb request. + +#. Lines 4-7 generate a WebOb Response object and return it. + + +Module ``run.py`` +--------------------- + +We need a small Python module that sets everything, fires up a web +server, and handles incoming requests. Later we'll see how to use a +Paste configuration file to do this work for us. + +.. literalinclude:: step01/run.py + :linenos: + +#. Line 1 uses the web server from the Paste project. + +#. Line 3 imports the big gun from ``repoze.bfg``. + +#. Line 4 grabs the function that hands back the top of the + application's model data. + +#. Line 5 imports the package that we want to "publish". + +#. Line 7 loads the big gun with the data and the package. + +#. And finally, line 8 starts the web server on port 5432. + + +Running and Browsing the Application +--------------------------------------- + +We have our minimal application now in place. We can run the +application as follows:: + + cd docs/step01 + python run.py + +.. note:: + + Just to say it AGAIN: make sure the Python you are using is the + Python from your virtual environment. One way to ensure you always + get the right scripts is to do ``source bin/activate`` from the top + of your virtualenv. This will modify your PATH to look first in the + virtual env. + +You should then see:: + + $ python ./run.py + serving on 0.0.0.0:5432 view at http://127.0.0.1:5432 + +If you connect in a web browser to ``http://localhost:5432/a`` you +will see:: + + Hello from a @ /a + +This also works for ``http://localhost:5432/b``. However, if you +connect to ``http://localhost:5432/c`` you will get:: + + 404 Not Found + The resource could not be found. + + http://localhost:5432/c
\ No newline at end of file diff --git a/docs/tutorials/lxmlgraph/step01/myapp/__init__.py b/docs/tutorials/lxmlgraph/step01/myapp/__init__.py new file mode 100644 index 000000000..792d60054 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step01/myapp/__init__.py @@ -0,0 +1 @@ +# diff --git a/docs/tutorials/lxmlgraph/step01/myapp/configure.zcml b/docs/tutorials/lxmlgraph/step01/myapp/configure.zcml new file mode 100644 index 000000000..b139396fa --- /dev/null +++ b/docs/tutorials/lxmlgraph/step01/myapp/configure.zcml @@ -0,0 +1,13 @@ +<configure xmlns="http://namespaces.zope.org/zope" + xmlns:bfg="http://namespaces.repoze.org/bfg"> + + <!-- this must be included for the view declarations to work --> + <include package="repoze.bfg" /> + + <!-- the default view for a MyModel --> + <bfg:view + for=".models.IMyModel" + view=".views.my_hello_view" + /> + +</configure> diff --git a/docs/tutorials/lxmlgraph/step01/myapp/models.py b/docs/tutorials/lxmlgraph/step01/myapp/models.py new file mode 100644 index 000000000..85d603d80 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step01/myapp/models.py @@ -0,0 +1,18 @@ +from zope.interface import implements +from zope.interface import Attribute +from zope.interface import Interface + +class IMyModel(Interface): + __name__ = Attribute('Name of the model instance') + +class MyModel(dict): + implements(IMyModel) + def __init__(self, name): + self.__name__ = name + +root = MyModel('site') +root['a'] = MyModel('a') +root['b'] = MyModel('b') + +def get_root(environ): + return root diff --git a/docs/tutorials/lxmlgraph/step01/myapp/views.py b/docs/tutorials/lxmlgraph/step01/myapp/views.py new file mode 100644 index 000000000..13de3ae31 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step01/myapp/views.py @@ -0,0 +1,7 @@ +from webob import Response + +def my_hello_view(context, request): + response = Response('Hello from %s @ %s' % ( + context.__name__, + request.environ['PATH_INFO'])) + return response diff --git a/docs/tutorials/lxmlgraph/step01/run.py b/docs/tutorials/lxmlgraph/step01/run.py new file mode 100644 index 000000000..1eac209dc --- /dev/null +++ b/docs/tutorials/lxmlgraph/step01/run.py @@ -0,0 +1,8 @@ +from paste import httpserver + +from repoze.bfg import make_app +from myapp.models import get_root +import myapp + +app = make_app(get_root, myapp) +httpserver.serve(app, host='0.0.0.0', port='5432') diff --git a/docs/tutorials/lxmlgraph/step02.rst b/docs/tutorials/lxmlgraph/step02.rst new file mode 100644 index 000000000..d4b6b261b --- /dev/null +++ b/docs/tutorials/lxmlgraph/step02.rst @@ -0,0 +1,118 @@ +================================================ +Step 02: Hello World as XML +================================================ + +We now have a website with ``/a`` and ``/b`` URLs. Each has a default +view that returns a teensy weensy response. + +In this step we will do the exact some scope, but using an XML +document as our model data. We will leverage the same ``repoze.bfg`` +machinery: + + - Model data with interfaces that define "types" + + - ZCML configuration to provide type-specific views + +We do, however, need to do some things differently: + + - Our model class needs to use lxml to inject itelf into the XML + nodes + + - That model class needs to implement the "handshake" + +Let's look at what changed. + +File ``myapp/samplemodel.xml`` +-------------------------------- + +Our hierarchy in Step 01 was very simple. Mimicking it in XML is, +thus, also very simple: + +.. literalinclude:: step02/myapp/samplemodel.xml + :linenos: + :language: xml + +#. Line 2 provides the root of the model as an XML ``<site>`` node. + The element name doesn't have to be ``<site>``. + +#. In lines 3-4, the ``<site>`` contains 2 top-level children: a and + b. These are provided as an element name ``<document>``. This, + also, is meaningfless as far as ``repoze.bfg`` is concerned. + However, this is where you compose th information model you are + publishing. + +The only special constraint is that a node that wants to be "found" by +``repoze.bfg`` in during traversal *must* have an ``name`` attribute. +(The use of ``@name`` corresponds to ``__name__`` in the +``repoze.bfg`` handshake.) Each hop in the URL tries to grab a child +with an attribute matching the next hop. Also, the value of the +``@name`` should be unique in its containing node. + + +Module ``myapp/models.py`` +------------------------------ + +Here is the serious change: we have made an XML-aware model. Or is it +a model-aware XML document? Such questions, harrumph. + +At a high level, we make write a class that "extends" lxml Element +nodes, create an lxml parser, and register the custom class with the +parser. + +.. literalinclude:: step02/myapp/models.py + :linenos: + +#. Line 4 imports lxml. + +#. Line 9 creates the custom class we are going to use to extend + etree.ElementBase. The lxml website has great documentation on the + various ways to inject custom Python behavior into XML. + +#. Just as before, line 12 says that instances of this class support a + certain content type (interface.) In our case, instances will be + XML nodes. + +#. ``repoze.bfg`` has a "protocol" where model data should have an + ``__name__`` attribute. Lines 14-16 implement this by grabbing the + ``@name`` attribute of the current node. + +#. URL traversal in ``repoze.bfg`` works via the ``__getitem__`` + protocol. Thus, we need a method that implements this. Lines + 18-26 use XPath to look for a direct child that has an ``@name`` + matching the item name that's being traversed to. If it finds it, + return it. If not, or if more than one is found, raise an error. + +#. As before, ``get_root`` is the function that is expected to return + the top of the model. In lines 30-32 we do the lxml magic to get + the custom Python class registered. We then load some XML and + return the top of the tree. + + +Module `myapp/views.py`` +-------------------------- + +We only made two changes here. + +.. literalinclude:: step02/myapp/views.py + :linenos: + +#. Line 5 grabs the element name (tag name) of the ``context``, which + is the current XML node that we're traversing through. + +#. Line 6 uses the special property we defined in our custom Python + class to get the ``__name__`` of the context. + + +Browsing the Model +------------------------ + +We can use the same URLs from Step 01 to browser the model and see +results:: + + http://localhost:5432/a + http://localhost:5432/b + http://localhost:5432/c (Not Found) + +In this case, each request grabs a node in the XML and uses it as the +data for the view. ``repoze.bfg`` doesn't really know that, unlike +Step 01, we no longer have "real" Python data.
\ No newline at end of file diff --git a/docs/tutorials/lxmlgraph/step02/myapp/__init__.py b/docs/tutorials/lxmlgraph/step02/myapp/__init__.py new file mode 100644 index 000000000..792d60054 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step02/myapp/__init__.py @@ -0,0 +1 @@ +# diff --git a/docs/tutorials/lxmlgraph/step02/myapp/configure.zcml b/docs/tutorials/lxmlgraph/step02/myapp/configure.zcml new file mode 100644 index 000000000..540526ec9 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step02/myapp/configure.zcml @@ -0,0 +1,11 @@ +<configure xmlns="http://namespaces.zope.org/zope" + xmlns:bfg="http://namespaces.repoze.org/bfg"> + + <include package="repoze.bfg" /> + + <bfg:view + for=".models.IMyModel" + view=".views.my_hello_view" + /> + +</configure> diff --git a/docs/tutorials/lxmlgraph/step02/myapp/models.py b/docs/tutorials/lxmlgraph/step02/myapp/models.py new file mode 100644 index 000000000..3c03de1a9 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step02/myapp/models.py @@ -0,0 +1,41 @@ +from zope.interface import implements +from zope.interface import Attribute +from zope.interface import Interface +from lxml import etree + +class IMyModel(Interface): + __name__ = Attribute('Name of the model instance') + +class BfgElement(etree.ElementBase): + """Handle access control and getitem behavior""" + + implements(IMyModel) + + @property + def __name__(self): + return self.xpath("@name")[0] + + def __getitem__(self, child_name): + xp = "*[@name='%s']" % child_name + matches = self.xpath(xp) + if len(matches) == 0: + raise KeyError('No child found for %s' % child_name) + elif len(matches) > 1: + raise KeyError('More than one child for %s' % child_name) + else: + return matches[0] + +def get_root(environ): + # Setup the custom parser with our BfgElement behavior + parser_lookup = etree.ElementDefaultClassLookup(element=BfgElement) + parser = etree.XMLParser() + parser.set_element_class_lookup(parser_lookup) + + # Now load the XML file + xmlstring = open("myapp/samplemodel.xml").read() + root = etree.XML(xmlstring, parser) + + return root + + + diff --git a/docs/tutorials/lxmlgraph/step02/myapp/samplemodel.xml b/docs/tutorials/lxmlgraph/step02/myapp/samplemodel.xml new file mode 100644 index 000000000..83678e5e9 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step02/myapp/samplemodel.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<site> + <document name="a"/> + <document name="b"/> +</site> diff --git a/docs/tutorials/lxmlgraph/step02/myapp/views.py b/docs/tutorials/lxmlgraph/step02/myapp/views.py new file mode 100644 index 000000000..205b4b163 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step02/myapp/views.py @@ -0,0 +1,8 @@ +from webob import Response + +def my_hello_view(context, request): + response = Response('Hello to %s from %s @ %s' % ( + context.tag, + context.__name__, + request.environ['PATH_INFO'])) + return response diff --git a/docs/tutorials/lxmlgraph/step02/run.py b/docs/tutorials/lxmlgraph/step02/run.py new file mode 100644 index 000000000..1eac209dc --- /dev/null +++ b/docs/tutorials/lxmlgraph/step02/run.py @@ -0,0 +1,8 @@ +from paste import httpserver + +from repoze.bfg import make_app +from myapp.models import get_root +import myapp + +app = make_app(get_root, myapp) +httpserver.serve(app, host='0.0.0.0', port='5432') diff --git a/docs/tutorials/lxmlgraph/step03.rst b/docs/tutorials/lxmlgraph/step03.rst new file mode 100644 index 000000000..c7298e41d --- /dev/null +++ b/docs/tutorials/lxmlgraph/step03.rst @@ -0,0 +1,175 @@ +================================================ +Step 03: Basic Rendering With ZPT and XSLT +================================================ + +Our XML-based model is now usable. However, we're using Python to +generate the HTML, instead of a template. In this step, we'll look at +wiring up some templates, using both ZPT and XSLT. + +In a nutshell, this means: + + - Slight changes to the ZCML + + - View functions that assemble information and call the template + +ZPT Templates +======================== + +Let's start with a ZPT-based default view for the nodes in the XML. +The ZCML for this would look like this: + +.. code-block:: xml + + <bfg:view + for=".models.IMyModel" + view=".views.zpt_default_view" + /> + +Here we point to a function in ``myapp/views.py`` that looks like the +following: + +.. code-block:: python + :linenos: + + from repoze.bfg.template import render_template_to_response + def zpt_default_view(context, request): + fn = "default.pt" + return render_template_to_response(fn, name=context.__name__, node=context) + +This function is relatively simple: + +#. Line 1 imports a ``repoze.bfg`` function that renders ZPT + templates. ``repoze.bfg`` uses the ``z3c.pt`` ZPT engine. + +#. Line 2, like our other view functions, gets passed a ``context`` + (the current hop in the URL) and WebOb ``request`` object. + +#. Line 3 points at the filename of the ZPT. + +#. Line 4 calls the ``render_template_to_response`` function, passing in the + filename for the ZPT and two top-level variables that can be used + in the ZPT. The first is the name of the current URL hop + (context). The second is the XML node object for that hop + (context). + +In Step 01 and 02, we returned a WebOb Response object that we +created. ``render_template_to_response`` makes a Response itself. + +Here's what the ZPT looks like: + +.. literalinclude:: step03/myapp/default.pt + :linenos: + :language: xml + +Look, a template! Life is better with templating: + +#. Lines 1-2 make an ``<html>`` node with a namespace for TAL. + +#. Line 5 inserts the value of the ``name`` that we passed into + ``render_template_to_response``. + +#. Line 6 sure looks interesting. It uses the ``node`` that we passed + in via ``render_template_to_response``. Since ``z3c.pt`` uses + Python as its expession language, we can put anything Python-legal + between the braces. And since ``node`` is an lxml Element object, + we just ask for its ``.tag``, like regular Python lxml code. + +Viewing the ZPT +------------------ + +With all of that in place, going to ``http://localhost:5432/a`` now +generates, via the ZPT, the following:: + + My template is viewing item: a + + The node has a tag name of: document. + + +XSLT Templates +==================== + +So that's the ZPT way of rendering HTML for an XML document. How +might XSLT look? + +.. note:: + + For the following, we'll switch back to showing the complete module + code, rather than snippets. You can then follow along by looking at + the files in ``docs/step03/myapp``. + +File ``myapp/configure.zcml`` +-------------------------------- + +The ZCML statement for the XSLT template looks almost exactly the same +as the ZPT template: + +.. literalinclude:: step03/myapp/configure.zcml + :linenos: + :language: xml + +#. Lines 10-14 wire up a new view, in addition to the default view. + +#. Line 13 provides the difference: ``name="xsltview.html"`` means + that all our URLs now can have ``/xsltview.xml`` appended to them. + +In the ZCML, there is no distinction between a ZPT view and an XSLT +view. The difference is only in the function that is pointed to by +the ``view=`` attribute. + + +Module ``myapp/views.py`` +-------------------------------- + +The ZCML says that our XSLT view (``xsltview.html`` on the URL) comes +from the ``myapp.views.xslt_view`` function: + +.. literalinclude:: step03/myapp/views.py + :linenos: + +#. Line 9 starts the Python function which serves as the view for this + template. The function has the same signature as the + ``zpt_default_view`` function we defined for the ZPT template's + view. + +#. Line 10 implements the difference. We call + ``render_transform_to_response`` instead of + ``render_template_to_response``. This tells ``repoze.bfg`` to make + an XSLT processor for this template, instead of a ZPT. The second + argument passes in ``context`` to the XSLT transform. ``context``` + is an instance of an Element node. Namely, a node from the XML + document that corresponds to the current hop in the URL. + + +File ``myapp/xsltview.xsl`` +-------------------------------- + +How different does the XSLT itself look? At this stage, not too different: + +.. literalinclude:: step03/myapp/xsltview.xsl + :linenos: + :language: xml + +#. Lines 1 and 2 are typical XSLT setup. + +#. Line 3 defines a rule to match on the node that is passed in. In + our case, a ``<document>`` node. + +#. Line 7 inserts the value of the ``@id`` attribute from the + "current" node at that point in the rule. We're sitting on the + ``<document>`` node (thanks to line 3). Thus, ``<xsl:value of + select="@id"/>`` inserts ``a`` or ``b``, depending on which + document we are sitting on. + +#. Line 8 shows the element name of the current node. + + +Viewing the XSLT +-------------------- + +With this in place, runnning the application provides a URL such as +``http://localhost:5432/a/xsltview.html``. Going to that URL should +show:: + + My template is viewing item: a + + The node has a name of: document. diff --git a/docs/tutorials/lxmlgraph/step03/myapp/__init__.py b/docs/tutorials/lxmlgraph/step03/myapp/__init__.py new file mode 100644 index 000000000..792d60054 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step03/myapp/__init__.py @@ -0,0 +1 @@ +# diff --git a/docs/tutorials/lxmlgraph/step03/myapp/configure.zcml b/docs/tutorials/lxmlgraph/step03/myapp/configure.zcml new file mode 100644 index 000000000..ef340c283 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step03/myapp/configure.zcml @@ -0,0 +1,17 @@ +<configure xmlns="http://namespaces.zope.org/zope" + xmlns:bfg="http://namespaces.repoze.org/bfg"> + + <include package="repoze.bfg" /> + + <bfg:view + for=".models.IMyModel" + view=".views.zpt_default_view" + /> + + <bfg:view + for=".models.IMyModel" + view=".views.xslt_view" + name="xsltview.html" + /> + +</configure> diff --git a/docs/tutorials/lxmlgraph/step03/myapp/default.pt b/docs/tutorials/lxmlgraph/step03/myapp/default.pt new file mode 100644 index 000000000..3cc98ef92 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step03/myapp/default.pt @@ -0,0 +1,8 @@ +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:tal="http://xml.zope.org/namespaces/tal"> + <head></head> + <body> + <h1>My template is viewing item: ${name}</h1> + <p>The node has a tag name of: ${node.tag}.</p> + </body> +</html> diff --git a/docs/tutorials/lxmlgraph/step03/myapp/models.py b/docs/tutorials/lxmlgraph/step03/myapp/models.py new file mode 100644 index 000000000..3c03de1a9 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step03/myapp/models.py @@ -0,0 +1,41 @@ +from zope.interface import implements +from zope.interface import Attribute +from zope.interface import Interface +from lxml import etree + +class IMyModel(Interface): + __name__ = Attribute('Name of the model instance') + +class BfgElement(etree.ElementBase): + """Handle access control and getitem behavior""" + + implements(IMyModel) + + @property + def __name__(self): + return self.xpath("@name")[0] + + def __getitem__(self, child_name): + xp = "*[@name='%s']" % child_name + matches = self.xpath(xp) + if len(matches) == 0: + raise KeyError('No child found for %s' % child_name) + elif len(matches) > 1: + raise KeyError('More than one child for %s' % child_name) + else: + return matches[0] + +def get_root(environ): + # Setup the custom parser with our BfgElement behavior + parser_lookup = etree.ElementDefaultClassLookup(element=BfgElement) + parser = etree.XMLParser() + parser.set_element_class_lookup(parser_lookup) + + # Now load the XML file + xmlstring = open("myapp/samplemodel.xml").read() + root = etree.XML(xmlstring, parser) + + return root + + + diff --git a/docs/tutorials/lxmlgraph/step03/myapp/samplemodel.xml b/docs/tutorials/lxmlgraph/step03/myapp/samplemodel.xml new file mode 100644 index 000000000..83678e5e9 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step03/myapp/samplemodel.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<site> + <document name="a"/> + <document name="b"/> +</site> diff --git a/docs/tutorials/lxmlgraph/step03/myapp/views.py b/docs/tutorials/lxmlgraph/step03/myapp/views.py new file mode 100644 index 000000000..c77eca8fe --- /dev/null +++ b/docs/tutorials/lxmlgraph/step03/myapp/views.py @@ -0,0 +1,10 @@ +from repoze.bfg.template import render_template_to_response +from repoze.bfg.template import render_transform_to_response + +def zpt_default_view(context, request): + return render_template_to_response("default.pt", + name=context.__name__, + node=context) + +def xslt_view(context, request): + return render_transform_to_response("xsltview.xsl", context) diff --git a/docs/tutorials/lxmlgraph/step03/myapp/xsltview.xsl b/docs/tutorials/lxmlgraph/step03/myapp/xsltview.xsl new file mode 100644 index 000000000..4d759b15b --- /dev/null +++ b/docs/tutorials/lxmlgraph/step03/myapp/xsltview.xsl @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> + <xsl:template match="/document"> + <html> + <head/> + <body> + <h1>My template is viewing item: <xsl:value-of select="@name"/></h1> + <p>The node has a name of: <xsl:value-of select="name()"/>.</p> + </body> + </html> + </xsl:template> +</xsl:stylesheet> diff --git a/docs/tutorials/lxmlgraph/step03/run.py b/docs/tutorials/lxmlgraph/step03/run.py new file mode 100644 index 000000000..1eac209dc --- /dev/null +++ b/docs/tutorials/lxmlgraph/step03/run.py @@ -0,0 +1,8 @@ +from paste import httpserver + +from repoze.bfg import make_app +from myapp.models import get_root +import myapp + +app = make_app(get_root, myapp) +httpserver.serve(app, host='0.0.0.0', port='5432') diff --git a/docs/tutorials/lxmlgraph/step04.rst b/docs/tutorials/lxmlgraph/step04.rst new file mode 100644 index 000000000..8112610a2 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step04.rst @@ -0,0 +1,98 @@ +================================================ +Step 04: Hierarchical Rendering With XSLT +================================================ + +Now that we have basic templating for our XML graph in place, let's +start doing some fun stuff with it. As we walk through use cases and +build out patterns for implementing them, we'll get to leverage some +features available in XML processors. For better or worse. [wink] + +In this step we take a look at the following: + +- Build a nested, folder-like website + +- Render the HTML common to all pages, then render the part specific + to a certain page + +- Show contents of a folder when sitting on a folder, and content of a + document when sitting on a document + + +Pre-Flight Cleanup +==================== + +In the last example, we had a default template that used ZPT. We're +shifting the rest of the steps over to XSLT. Thus, our +``myapp/configure.zcml`` is now simpler: + +.. literalinclude:: step04/myapp/configure.zcml + :linenos: + :language: xml + +We also remove the ZPT view function from ``views.py``, as we'll see +in a moment. + +Design Change: Trees and Context IDs +======================================== + +In ``repoze.bfg``, the ``context`` variable that is passed into our +view function equates to the Python object that was grabbed on the +current hop in the URL. For ``repoze.lxmlgraph``, that "context" +object is a node in the XML document, found by traversing node +children. + +For the XSLT in Step 03, we passed in the context node. From the +XSLT's perpective, the universe started at the context node. It could +only see information in that node and the children beneath it. + +If we could see the entire tree, however, we could put the other +information to use: showing the name of the site in a header, listing +the breadcrumbs to reach the document, and other portal-style boxes. + +To enable this, we need the following: + +#. A way to pass in the entire XML document tree. + +#. A way to uniquely point at the item in the XML that we are + currently sitting on, with the fastest performance possible. + +We will thus make the following changes in our approach: + +#. The XML document will support an ``xml:id`` attribute on each node + that has a ``name`` attribute. The ``xml:id`` uniquely identifies + the resource within the document. Moreover, it leverages built-in + support for high-speed lookups in XPath. + +#. We change the view function to pass in the root of the tree, + instead of the context node. + +#. We also pass in, via an XSLT parameter, the ``xml:id`` of the + context node. + +#. The XSLT will start at the top of the tree, generate the site-wide + look and feel, then render the context node. + +That's the big picture. Each of these changes will be explained in +detail below. + + +File ``myapp/samplemodel.xml`` +================================ + +The XML document with the information for our website has quite a +number of changes: + +.. literalinclude:: step04/myapp/samplemodel.xml + :linenos: + :language: xml + +#. Line 3 shows that our ``<site>`` now gets a ``<title>``. + +#. On Line 4 we make an index document at the root that contains a + document-wide unique value for its ``@xml:id``. + +#. In lines 5-11, our ``<document>`` gets some extra information: a + ``<title>``, plus some HTML-namespaced markup content inside a + ``<body>``. + +#. diff --git a/docs/tutorials/lxmlgraph/step04/myapp/__init__.py b/docs/tutorials/lxmlgraph/step04/myapp/__init__.py new file mode 100644 index 000000000..792d60054 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step04/myapp/__init__.py @@ -0,0 +1 @@ +# diff --git a/docs/tutorials/lxmlgraph/step04/myapp/configure.zcml b/docs/tutorials/lxmlgraph/step04/myapp/configure.zcml new file mode 100644 index 000000000..1ba4c9155 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step04/myapp/configure.zcml @@ -0,0 +1,11 @@ +<configure xmlns="http://namespaces.zope.org/zope" + xmlns:bfg="http://namespaces.repoze.org/bfg"> + + <include package="repoze.bfg" /> + + <bfg:view + for=".models.IMyModel" + view=".views.xslt_view" + /> + +</configure> diff --git a/docs/tutorials/lxmlgraph/step04/myapp/models.py b/docs/tutorials/lxmlgraph/step04/myapp/models.py new file mode 100644 index 000000000..3c03de1a9 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step04/myapp/models.py @@ -0,0 +1,41 @@ +from zope.interface import implements +from zope.interface import Attribute +from zope.interface import Interface +from lxml import etree + +class IMyModel(Interface): + __name__ = Attribute('Name of the model instance') + +class BfgElement(etree.ElementBase): + """Handle access control and getitem behavior""" + + implements(IMyModel) + + @property + def __name__(self): + return self.xpath("@name")[0] + + def __getitem__(self, child_name): + xp = "*[@name='%s']" % child_name + matches = self.xpath(xp) + if len(matches) == 0: + raise KeyError('No child found for %s' % child_name) + elif len(matches) > 1: + raise KeyError('More than one child for %s' % child_name) + else: + return matches[0] + +def get_root(environ): + # Setup the custom parser with our BfgElement behavior + parser_lookup = etree.ElementDefaultClassLookup(element=BfgElement) + parser = etree.XMLParser() + parser.set_element_class_lookup(parser_lookup) + + # Now load the XML file + xmlstring = open("myapp/samplemodel.xml").read() + root = etree.XML(xmlstring, parser) + + return root + + + diff --git a/docs/tutorials/lxmlgraph/step04/myapp/samplemodel.xml b/docs/tutorials/lxmlgraph/step04/myapp/samplemodel.xml new file mode 100644 index 000000000..36ab335d2 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step04/myapp/samplemodel.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<site> + <title>My XMLGRAPH Website</title> + <document xml:id="index" name="index.html"> + <title>Site Home Page</title> + <body> + <div xmlns="http://www.w3.org/1999/xhtml"> + <p>Welcome to the site. We have lots to say.</p> + <p>Or, <em>maybe</em> not.</p> + </div> + </body> + </document> + <folder xml:id="n1" name="folder1"> + <title>Folder One</title> + <document xml:id="n11" name="doc1"> + <title>doc1 in folder1</title> + <body> + <div xmlns="http://www.w3.org/1999/xhtml"> + <p>I am in an HTML <code>div</code> so I can do <strong>LOTS</strong> of + formatting.</p> + </div> + </body> + </document> + <document xml:id="n12" name="doc2"> + <title>doc2 in folder1</title> + <body> + <div xmlns="http://www.w3.org/1999/xhtml"> + <p>Keep this on one line.</p> + </div> + </body> + </document> + </folder> + <folder xml:id="n2" name="folder2"> + <title>The Second Folder</title> + <document xml:id="n21" name="doc1"> + <title>doc1 in folder2</title> + <body> + <div xmlns="http://www.w3.org/1999/xhtml"> + <p>This is a special folder. It's folder 2!</p> + </div> + </body> + </document> + </folder> +</site> diff --git a/docs/tutorials/lxmlgraph/step04/myapp/views.py b/docs/tutorials/lxmlgraph/step04/myapp/views.py new file mode 100644 index 000000000..fd8650e14 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step04/myapp/views.py @@ -0,0 +1,13 @@ +from repoze.bfg.template import render_transform_to_response + +# Some constants +XML_NAMESPACE='http://www.w3.org/XML/1998/namespace' +XML_PREFIX= '{%s}' % XML_NAMESPACE + +def xslt_view(context, request): + # Grab the root of the tree, which should be a <site> + site = context.getroottree().getroot() + # Jot down which node we're sitting on as the <context> + contextid = "'%s'" % context.get(XML_PREFIX+'id') + return render_transform_to_response("xsltview.xsl", site, + contextid=contextid) diff --git a/docs/tutorials/lxmlgraph/step04/myapp/xsltview.xsl b/docs/tutorials/lxmlgraph/step04/myapp/xsltview.xsl new file mode 100644 index 000000000..2406987d0 --- /dev/null +++ b/docs/tutorials/lxmlgraph/step04/myapp/xsltview.xsl @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> + <xsl:param name="contextid">n1</xsl:param> + <xsl:variable name="contextnode" select="id($contextid)"/> + <xsl:template match="/"> + <html> + <head> + <title> + <xsl:value-of select="$contextnode/title"/> + </title> + </head> + <body> + <h2> + <xsl:value-of select="$contextnode/title"/> + </h2> + <xsl:apply-templates select="$contextnode"/> + <table border="1" cellpadding="6" cellspacing="0"> + <tr> + <th>Type</th> + <th>@xml:id</th> + <th>@name</th> + <th>Parent Type</th> + <th>Parent @name</th> + </tr> + <tr> + <td> + <xsl:value-of select="name($contextnode)"/> + </td> + <td> + <xsl:value-of select="$contextnode/@xml:id"/> + </td> + <td> + <xsl:value-of select="$contextnode/@name"/> + </td> + <td> + <xsl:value-of select="name($contextnode/..)"/> + </td> + <td> + <xsl:value-of select="$contextnode/../@name"/> + </td> + </tr> + </table> + </body> + </html> + </xsl:template> + <xsl:template match="folder"> + <p> + <em>Folders are special, they contain things.</em> + </p> + <xsl:if test="*[@xml:id]"> + <h2>Folder Contents</h2> + <ul> + <xsl:for-each select="*[@xml:id]"> + <li> + <a href="{../@name}/{@name}"> + <xsl:value-of select="title"/> + </a> + </li> + </xsl:for-each> + </ul> + </xsl:if> + </xsl:template> + <xsl:template match="document"> + <p> + <em>Documents contain text.</em> + </p> + <xsl:copy-of select="body/*"/> + </xsl:template> +</xsl:stylesheet> diff --git a/docs/tutorials/lxmlgraph/step04/run.py b/docs/tutorials/lxmlgraph/step04/run.py new file mode 100644 index 000000000..1eac209dc --- /dev/null +++ b/docs/tutorials/lxmlgraph/step04/run.py @@ -0,0 +1,8 @@ +from paste import httpserver + +from repoze.bfg import make_app +from myapp.models import get_root +import myapp + +app = make_app(get_root, myapp) +httpserver.serve(app, host='0.0.0.0', port='5432') diff --git a/docs/tutorials/lxmlgraph/step05.rst b/docs/tutorials/lxmlgraph/step05.rst new file mode 100644 index 000000000..83474adfd --- /dev/null +++ b/docs/tutorials/lxmlgraph/step05.rst @@ -0,0 +1,12 @@ +================================================ +Step 05: Advanced Templating +================================================ + + +- multiple "views" wired to the same function and template + +- breadcrumbs, sidebars + +- <query> support + +- type-based support for <document>, <folder>, <query>, <site>
\ No newline at end of file |
