Problem mit Cheetah Vorlagenvererbung

Django, Flask, Bottle, WSGI, CGI…
Antworten
Benutzeravatar
tjuXx
User
Beiträge: 67
Registriert: Freitag 21. September 2007, 09:25
Wohnort: Bremerhaven
Kontaktdaten:

Hallo,
vorweg möchte ich mal ein großes Dankeschön an Gerold, für seinen wunderbaren: "Erfahrungsbericht - CherryPy und Cheetah", richten.

Zu meinem Problem:
Ich habe mir für meine gesamte Struktur eine Hauptvorlage (main.tmpl) erstellt:

Code: Alles auswählen

#encoding UTF-8

#attr title     = "Home"
#attr content   = "<p>Herzlich Willkommen</p>"
#attr $rName1   = 'No Data'
#attr $rName2   = 'No Data'
#attr $rName3   = 'No Data'
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
       "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head><title>... - $title</title></head>
<body>
<div id="main"><div id="left"></div>
   <div id="center">
      <ul class="nav">
         <li class="home"></li>
         <li class="something"></li>
         <li class="recent">
            <ul class="recent">
               <li class="top">$rName1</li>
               <li>$rName2</li>
               <li>$rName3</li>
            </ul>
         </li>
      </ul>
      #block login
         <form id="login">...</form>
      #end block login
   </div><div id="right"></div>
   <div id="content">

      #block C
      <h1>$title</h1>
      $content
      #end block C

   </div>
</div>
</body>
</html>
Der Block C soll dann je nach aufgerufener Seite überschrieben werden. Bsp (imprint.tmpl):

Code: Alles auswählen

#encoding UTF-8

#extends main
#attr title = "Impressum"
#attr content = "<p>Name<br>Strasse<br>D-PLZ Ort</p>"

#block C
  <h1>$title</h1>
  $content
#end block C
Mein Problem ist jetzt, dass ich nicht weiß wie ich die Vorlagen mit Cherrypy SAUBER befüllen kann. Hier zunächst mal mein Cherry-Skript (maincherry.py):

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: utf-8 -*-

import cherrypy
import os
from Cheetah.Template import Template

APPDIR = os.path.dirname(os.path.abspath(__file__))
INI_FILENAME = os.path.join(APPDIR, "maincherry.ini")


class Root(object):
   
   def index(self):
      template = Template(file = os.path.join(APPDIR, "index.tmpl"))
      return str(template)
   index.exposed = True

   def imprint(self):
      template = Template(file = os.path.join(APPDIR, "imprint.tmpl"))
      return str(template)
   imprint.exposed = True
   

def main():
    cherrypy.quickstart(Root(), config = INI_FILENAME)

if __name__ == "__main__":
    main()
Ich könnte jetzt natürlich in jeder einzelnen Methode die recent-Platzhalter ($rName1,...) füllen, allerdings halt ich das nicht für Sauber, da es für alle Methoden die selben Werte sind. Ich würde gerne zuerst das Main.tmpl füllen, und dann je nach Bedarf den "Block C". Leider komme ich auf keine Idee wie ich dies bewerkstelligen kann. Ich hoffe es kann mir hierbei jemand auf die Sprünge helfen.

Gruß tjuXx
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Hallo tjuXx!

Ich muss auf meinem Computer erst mal eine 'gscheide IDE installieren. Dann schau' ich mal, ob ich ein kleines Beispiel basteln kann.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Hallo tjuXx!

Ganz so klein ist das Beispiel jetzt doch nicht geworden. Ich habe ein paar Dinge ausprobiert und versucht eine Best-Practice auszuarbeiten. Außerdem habe ich das Beispiel mit ein paar Goodies bestückt, die vielleicht nicht uninteressant sind.

Es gibt mehrere Möglichkeiten, Vorlagen miteinander zu kombinieren.

Eine davon ist die ``#include``-Anweisung in der Cheetah-Vorlage selbst. Mit ``#include`` kann man andere Cheetah-Vorlagen in die Vorlage importieren. Die importierte Vorlage wird wie ein Teil der aktuellen Vorlage behandelt. Das heißt, dass die dynamischen Bereiche der importierten Vorlage so ausgeführt werden, als ob sie direkt in der aktuellen Vorlage stehen würden.

Diesen Mechanismus verwende ich gerne für Teile, die ich gerne dynamisch anpassen möchte, ohne die Hauptvorlage dafür ändern zu müssen. Z.B. für den Header oder den Footer.

Die Verwendung von ``#include`` ist ziemlich einfach. Nach ``#include`` gibt man den Pfad zur einzubindenden Vorlage, relativ zum Anwendungsordner an. Der Anwendungsordner ist der Ordner, in dem sich das Programm befindet, welches die Vorlagen rendert und anzeigt. z.B. ``#include "templates/pagetemplate/header.tmpl"``

Weiters kann man in eine Vorlage direkt dynamischen Content einbinden. Man kann also von der Anwendung HTML erzeugen lassen, der in die Vorlage "dynamisch" eingebunden wird. Das Einbinden von dynamischen Werten passiert in Cheetah-Vorlagen hauptsächlich über das "$"-Zeichen, gefolgt von einem Namen eines Python-Objektes das Text zurück liefern kann. Das kann z.B. der Name einer Variable oder einer Funktion sein.

Interessant dabei ist, dass es sich auch um eine Instanz einer Cheetah-Vorlage handeln kann, die vom Programm an die Vorlage übergeben wurde. Z.B: ``$mainnav`` oder ``$site_title - $page_title``

Dann gibt es noch die Vererbung selbst. In der Hauptvorlage werden Bereiche markiert, die von einer (ich nenne sie jetzt einfach mal so) Untervorlage überschrieben werden können.

In der Hauptvorlage kann man Bereiche z.B. mit ``#block`` markieren. Gibt es in der Untervorlage ebenfalls einen solchen Bereich, dann überschreibt dieser den Bereich der Hauptvorlage an der dort markierten Stelle.

Man kann auch Klassenattribute (mit ``#attr``) erstellen, diese in der Untervorlage überschreiben und in der Hauptvorlage an den verschiedensten Stellen verwenden. Allerdings ist das nicht so ideal, da es hier Probleme mit Unicode gibt. Deshalb habe ich seit einiger Zeit auf die Verwendung von ``#def`` umgestellt. Die Verwendung ist ähnlich einfach wie die Verwendung von ``#attr``.

Statt ``#attr $name = "Ein Text"`` verwende ich jetzt ``#def $name: Ein Text mit Umlauten (ÖÄÜ)``. Nur so nebenbei bemerkt: Ich verwende in meinen Projekten ``gettext`` um die Anwendungen in mehrere Sprachen zu übersetzen. Das lässt sich auf diese Art ziemlich einfach in die Vorlagen integrieren. Ein zu übersetzender Text wird mit ``$_()`` markiert (z.B. so: ``#def $name: $_("Hallo Welt")``). Ein kleines Skript erstellt aus allen Cheetah-Vorlagen Python-Module, dann werden die markierten Texte per ``gettext`` herausgeholt und in eine POT-Datei geschrieben. Danach werden die temporär erstellten Python-Module wieder gelöscht. :-) Das funktioniert wirklich wunderbar. :-)
Und jetzt zurück zur Vererbung. ;-)

Soll eine Untervorlage von einer Hauptvorlage erben, dann wird dies mit ``#extends`` eingeleitet. Als Parameter wird, wie bei ``import`` der Name des Paketes und/oder Modules übergeben. Die Hauptvorlage kann mit dem Kommandozeilenprogramm ``cheetah`` zu einem Python-Modul kompiliert werden. Man kann die Hauptvorlage aber auch im Programm selbst kompilieren lassen. Zuerst wird mit ``Cheetah.Template.Template.compile()`` die Hauptvorlage gerendert. Danach kann man die damit erstellte Klasse mit ``.subclass()`` um die Untervorlage erweitern. Damit das funktioniert, muss man beim Aufruf von ``.compile()`` nur auch den Modulnamen (moduleName = "...") übergeben, der in der Untervorlage mit
``#extends <modulname>`` angegeben wird. Das ist deshalb wichtig, weil die Hauptvorlage nicht als Python-Modul direkt über das Dateisystem zur Verfügung steht. Wie das alles funktioniert, habe ich ins Beispiel mit eingebaut.

Und jetzt noch zu einem Punkt, den ich auch ins Beispiel eingebaut habe --> die Verwendung von Unicode als Basis.

Über die INI-Datei habe ich CherryPy so eingestellt, dass jeder Unicode-String, der von den Request-Handlern zurück gegeben wird, automatisch nach UTF-8 umgewandelt wird. Eine unbedingt notwendige Einstellung, denke ich, wenn man sauber arbeiten möchte.

Und hier das Beispiel:

cpapp.ini
cpapp.py
cpapp_wsgi.py
http_root/
http_root/__init__.py
http_root/index.tmpl
http_root/css/
http_root/css/main.css
http_root/ordner1/
http_root/ordner1/__init__.py
http_root/ordner1/index.tmpl
http_root/ordner2/
http_root/ordner2/__init__.py
http_root/ordner2/index.tmpl
lib/
lib/__init__.py
lib/constants.py
log/
templates/
templates/__init__.py
templates/mainnav/
templates/mainnav/__init__.py
templates/mainnav/template.tmpl
templates/pagetemplate/
templates/pagetemplate/__init__.py
templates/pagetemplate/template.tmpl
templates/pagetemplate/header.tmpl
templates/pagetemplate/footer.tmpl

Natürlich gibt es wieder mal mehrer Möglichkeiten um so ein Projekt umzusetzen. Ich wollte nur "eine" davon aufzeigen.

Das Beispiel als ZIP-Datei: http://halvar.at/krimskrams3/cherrypy_c ... xample.zip

mfg
Gerold
:-)
Zuletzt geändert von gerold am Sonntag 15. August 2010, 16:05, insgesamt 1-mal geändert.
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Hallo tjuXx!

Ich wollte *templates.pagetemplate.__init__.py* noch ein wenig erklären.

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf-8

import os
import cherrypy
from Cheetah.Template import Template as CheetahTemplate
import templates.mainnav

THISDIR = os.path.dirname(os.path.abspath(__file__))

BaseTemplate = CheetahTemplate.compile(
    file = os.path.join(THISDIR, "template.tmpl"),
    moduleName = "page_basetemplate"
)


def get_page_template(subtemplate_filepath):
    
    # Neue Vorlage von Hauptvorlage ableiten
    template = BaseTemplate.subclass(file = subtemplate_filepath)()

    # INI-Encoding
    inicoding = cherrypy.config["coding"]

    # Site-Title (als Unicode-String aus der INI lesen)
    template.site_title = cherrypy.config["site_title"].decode(inicoding)
    
    # Hauptmenü-Portlet
    template.mainnav = templates.mainnav.get_mainnav_template()
    
    # Rückgabe
    return template
Also, dieser Code hier ...

Code: Alles auswählen

BaseTemplate = CheetahTemplate.compile(
    file = os.path.join(THISDIR, "template.tmpl"),
    moduleName = "page_basetemplate"
)
... liest die Hauptvorlage aus dem Dateisystem aus und macht daraus eine Vorlagenklasse. Als Modulname wird hier "page_basetamplate" festgelegt, den man in der Untervorlage verwenden kann um auf diese Hauptvorlage zugreifen zu können. Z.B. mit ``#extends page_basetemplate``. Da das auf Modul-Ebene passiert, wird die Vorlage nur einmal, beim Start der Anwendung, gerendert.

Die Hauptvorlage wurde bereits beim Starten der Anwendung gerendert. Die Arbeit besteht jetzt nur noch darin, eine Untervorlage mit der Hauptvorlage zu kombinieren und diese Vorlagenkombination mit dynamischen Daten zu befüllen.

Dieser Code hier ...

Code: Alles auswählen

template = BaseTemplate.subclass(file = subtemplate_filepath)()
... nimmt die Hauptvorlagenklasse und "rendert" die Untervorlage mit rein. Das Ergebnis ist eine neue Klasse, aus der mit ``()`` eine neue Klasseninstanz (template) wird.

Jetzt haben wir eine Klasseninstanz einer Cheetah-Vorlage. Diese kann jetzt mit Attributen bestückt werden. Z.B. kann man den "Site-Title" an die Vorlage übergeben. Dieser "Site-Title" wird in diesem Fall aus der INI-Datei ausgelesen. Ich habe die INI-Datei im UTF-8-Encoding abgespeichert. Texte die ich also aus der INI-Datei auslese, sind in meinem Fall UTF-8-codiert. Ich habe das einstellbar gemacht. Das Encoding steht auch in der INI-Datei.

Mit ...

Code: Alles auswählen

template.site_title = cherrypy.config["site_title"].decode(inicoding)
... wird die Einstellung "site_title" aus der INI-Datei ausgelesen und mit ``.decode(...)`` nach Unicode umgewandelt. So kann an die Vorlage gezielt Unicode übergeben werden. Alles was ins Programm rein kommt wird Unicode. Und alles was das Programm verlässt wird von CherryPy automatisch in das gewünschte Encoding umgewandelt.

Dieser Code hier ...

Code: Alles auswählen

template.mainnav = templates.mainnav.get_mainnav_template()
... holt sich eine weitere, fertig gerenderte Cheetah-Vorlage (der Code dazu befindet sich in einem anderen Python-Modul), die bereits mit allen Daten bestückt ist, und übergibt sie als Attribut an die Vorlage. Innerhalb der TMPL-Datei kann diese mit ...

Code: Alles auswählen

$mainnav
... abgerufen werden.

Die Verwendung der Funktion *get_page_template* ist ziemlich einfach. An *get_page_template* wird der Pfad zur Untervorlage (=TMPL-Datei) übergeben. Zurück bekommt man eine Cheetah-Vorlage, die bereits mit den wichtigsten Daten bestückt wurde. Man muss jetzt nur noch die Daten an die Vorlage übergeben, die von der Untervorlage benötigt werden. Mit ``unicode(template)`` wird aus der Cheetah-Vorlage ein Unicode-String, in den alle dynamischen Daten eigearbeitet wurden. Die Request-Handler-Funktion muss diesen Unicode-String jetzt nur noch mit ``return`` zurück geben und schon liefert CherryPy die HTML-Seite an den Browser aus.

Code: Alles auswählen

template = get_page_template("<Pfad zur Untervorlage>")
template.hallo = u"Hallo"
template.welt = u"Welt"
return unicode(template)
mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Antworten