Dies ist die Support Website des Buches:: Das Python Praxisbuch Der große Profi-Leitfaden für Programmierer Farid Hajji Addison Wesley / Pearson Education ISBN 978-3-8273-2543-3 (Sep 2008), 1298 Seiten. ****************************************** 15. Webprogrammierung und Web Frameworks ****************************************** Webserver in Python =================== Webserver aus der Python Standard Library ----------------------------------------- BaseHTTPServer ^^^^^^^^^^^^^^ .. literalinclude:: ../att/webprog/basehttpserver.py `basehttpserver.py `_ .. literalinclude:: ../att/webprog/basehttpserver2.py `basehttpserver2.py `_ .. literalinclude:: ../att/webprog/basehttpcalc.py `basehttpcalc.py `_ SimpleHTTPServer ^^^^^^^^^^^^^^^^ .. literalinclude:: ../att/webprog/simplehttpserver.py `simplehttpserver.py `_ CGIHTTPServer ^^^^^^^^^^^^^ .. literalinclude:: ../att/webprog/cgihttpserver.py `cgihttpserver.py `_ .. literalinclude:: ../att/webprog/cgiprintenv.py `cgiprintenv.py `_ wsgiref.simple_server ^^^^^^^^^^^^^^^^^^^^^ Eine einfache WSGI-Anwendung:: def demo_app(environ,start_response): from StringIO import StringIO stdout = StringIO() print >>stdout, "Hello world!" print >>stdout h = environ.items(); h.sort() for k,v in h: print >>stdout, k,'=',`v` start_response("200 OK", [('Content-Type','text/plain')]) return [stdout.getvalue()] .. literalinclude:: ../att/webprog/wsgidemo.py `wsgidemo.py `_ .. literalinclude:: ../att/webprog/wsgicalc.py `wsgicalc.py `_ Webserver aus Drittanbietermodulen ---------------------------------- Webserver mit CherryPy ^^^^^^^^^^^^^^^^^^^^^^ URLs: * `Die CherryPy Website `_ (einfacher: mit ``easy_install CherryPy`` installieren) * `Manpage des Programms fetch `_ Screenshots: * `Screenshot der CherryPy Website `_ .. literalinclude:: ../att/webprog/cherrypyhello.py `cherrypyhello.py `_ .. literalinclude:: ../att/webprog/cherrypymapper.py `cherrypymapper.py `_ .. literalinclude:: ../att/webprog/cherrypymapper2.py `cherrypymapper2.py `_ .. literalinclude:: ../att/webprog/cherrypysite.py `cherrypysite.py `_ .. literalinclude:: ../att/webprog/cherrypyform.py `cherrypyform.py `_ .. literalinclude:: ../att/webprog/cherrypycalc.py `cherrypycalc.py `_ Twisted.Web Server ^^^^^^^^^^^^^^^^^^ URLs: * `Grundlage dieses Abschnitts ist dieses HOWTO `_ .. literalinclude:: ../att/webprog/twistedhelloworld.py `twistedhelloworld.py `_ .. literalinclude:: ../att/webprog/twistedurl1.py `twistedurl1.py `_ .. literalinclude:: ../att/webprog/twistedurl2.py `twistedurl2.py `_ .. literalinclude:: ../att/webprog/twistedwebcalc.py `twistedwebcalc.py `_ .. literalinclude:: ../att/webprog/twistedwebcalc2.py `twistedwebcalc2.py `_ .. literalinclude:: ../att/webprog/twistedwebcalc3.py `twistedwebcalc3.py `_ .. literalinclude:: ../att/webprog/twistedstaticfile.py `twistedstaticfile.py `_ .. literalinclude:: ../att/webprog/twistedstaticandcgi.py `twistedstaticandcgi.py `_ .. literalinclude:: ../att/webprog/twistedserverpy.py `twistedserverpy.py `_ .. literalinclude:: ../att/webprog/webcalc.rpy :language: python `webcalc.rpy `_ .. literalinclude:: ../att/webprog/twistedserve_rpy_perl_php.py `twistedserve_rpy_perl_php.py `_ .. literalinclude:: ../att/webprog/test.pl :language: perl `test.pl `_ .. literalinclude:: ../att/webprog/test.php :language: php `test.php `_ .. literalinclude:: ../att/webprog/twistedtarpit.py `twistedtarpit.py `_ .. literalinclude:: ../att/webprog/twistedwebsvc.py `twistedwebsvc.py `_ Integration mit anderen Webservern ================================== Lighttpd -------- URLs: * `Website von Lighttpd `_ `lighttpd_common.conf `_ Lighttpd und CGI ^^^^^^^^^^^^^^^^ .. literalinclude:: ../att/webprog/lighttpd_cgi.conf `lighttpd_cgi.conf `_ Lighttpd und FastCGI ^^^^^^^^^^^^^^^^^^^^ .. literalinclude:: ../att/webprog/lighttpd_fastcgi.conf `lighttpd_fastcgi.conf `_ FastCGI-Server in Python mit flup ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ URLs: * `Das flup Modul `_ (mit ``easy_install flup`` installieren) .. literalinclude:: ../att/webprog/fastcgi_server_helloworld.py `fastcgi_server_helloworld.py `_ .. literalinclude:: ../att/webprog/fastcgi_server_debug.py `fastcgi_server_debug.py `_ .. literalinclude:: ../att/webprog/fastcgi_server_webcalc.py `fastcgi_server_webcalc.py `_ Lighttpd und SCGI ^^^^^^^^^^^^^^^^^ .. literalinclude:: ../att/webprog/lighttpd_scgi.conf `lighttpd_scgi.conf `_ SCGI-Server in Python mit flup ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. literalinclude:: ../att/webprog/scgi_server_webcalc.py `scgi_server_webcalc.py `_ Python in Lighttpd integrieren ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Apache ------ URLs: * `Website von Apache `_ Apache und CGI ^^^^^^^^^^^^^^ Apache und FastCGI ^^^^^^^^^^^^^^^^^^ URLs: * `Website des mod_fastcgi Moduls `_ Apache und SCGI ^^^^^^^^^^^^^^^ URLs: * `Website des mod_scgi Moduls `_ Apache und mod_python ^^^^^^^^^^^^^^^^^^^^^ URLs: * `Extending and Embedding `_ * `Website von mod_python `_ Wie das Einbetten eines Python-Interpreters in C-Code funktioniert: .. code-block:: c #include int main(int argc, char *argv[]) { Py_Initialize(); PyRun_SimpleString("from time import time, ctime\n" "print 'Today is', ctime(time())\n"); Py_Finalize(); return 0; } Ein Ausschnitt aus der :file:`httpd.conf`:: LoadModule python_module libexec/apache22/mod_python.so AddHandler mod_python .py PythonHandler mod_python.publisher PythonDebug On .. literalinclude:: ../att/webprog/modpycalc.py `modpycalc.py `_ Apache und WSGI ^^^^^^^^^^^^^^^ URLs: * `Website des mod_wsgi Moduls `_ Ein Ausschnitt aus der :file:`httpd.conf`:: LoadModule wsgi_module libexec/apache22/mod_wsgi.so LogLevel info # 1. /wsgi should contain WSGI-apps. # Note: the function name MUST be 'application' WSGIScriptAlias /wsgi/ /usr/local/www/apache22/wsgi-bin/ # 2. Additional paths to look for python modules (:-separated) WSGIPythonPath "/usr/local/www/apache22/wsgi-bin" # 3. All applications within this directory will share the # same python subinterpreter. WSGIApplicationGroup my-wsgi-scripts Order allow,deny Allow from all .. literalinclude:: ../att/webprog/hello.wsgi :language: python `hello.wsgi `_ .. literalinclude:: ../att/webprog/wsgidebug.py `wsgidebug.py `_ .. literalinclude:: ../att/webprog/debug.wsgi :language: python `debug.wsgi `_ .. literalinclude:: ../att/webprog/calc.wsgi :language: python `calc.wsgi `_ WSGI ==== Was ist WSGI? ------------- .. literalinclude:: ../att/webprog/hello.wsgi :language: python `hello.wsgi `_ .. literalinclude:: ../att/webprog/wsgidummyapp.py `wsgidummyapp.py `_ .. literalinclude:: ../att/webprog/wsgidummyserver.py `wsgidummyserver.py `_ .. literalinclude:: ../att/webprog/wsgidummydemo.py `wsgidummydemo.py `_ URLs: * `Informationen zu WSGI `_ WSGI-Tools ---------- Die wsgiref.*-Module ^^^^^^^^^^^^^^^^^^^^ WSGI-Utils ^^^^^^^^^^ URLs: * `Website der WSGI-Utils `_ (aber mit ``easy_install wsgiutils`` installieren) flup ^^^^ Wurde weiter oben schon eingeführt. Python Paste ^^^^^^^^^^^^ URLs: * `Website von Paste `_ Low-level-Programmierung mit CGI ================================ Hello, CGI World! ----------------- .. literalinclude:: ../att/webprog/cgihello.py `cgihello.py `_ CGI-Umgebungsvariablen ---------------------- .. literalinclude:: ../att/webprog/cgiprintenv.py `cgiprintenv.py `_ Screenshots: * `Ausgabe von cgiprintenv.py `_ Anwendung: Ein Web-Taschenrechner --------------------------------- .. literalinclude:: ../att/webprog/cgicalc.py `cgicalc.py `_ Ein Formular manuell auslesen ----------------------------- .. literalinclude:: ../att/webprog/cgiformget.html :language: xml `cgiformget.html `_ .. literalinclude:: ../att/webprog/cgiform.py `cgiform.py `_ .. literalinclude:: ../att/webprog/cgiformpost.html :language: xml `cgiformpost.html `_ Das cgi-Modul ------------- .. literalinclude:: ../att/webprog/cgiform2.py `cgiform2.py `_ .. literalinclude:: ../att/webprog/cgierror.py `cgierror.py `_ Screenshots: * `Ausgabe von cgierror.py `_ Anwendung: Dateien uploaden --------------------------- .. literalinclude:: ../att/webprog/cgiupload.py `cgiupload.py `_ Das Formular, welches das Eingabefenster erzeugt: .. code-block:: xml A simple file upload form

Upload a file to /tmp

(File Name)
(User Password)
Den Zustand clientseitig erhalten --------------------------------- Zustand als Teil der URL ^^^^^^^^^^^^^^^^^^^^^^^^ .. literalinclude:: ../att/webprog/cgistate_pathinfo.py `cgistate_pathinfo.py `_ hidden-Felder ^^^^^^^^^^^^^ Ein Formular mit einem Hidden-Feld: .. code-block:: xml CGI Counter 2

Current counter value: 4

.. literalinclude:: ../att/webprog/cgistate_hiddenfields.py `cgistate_hiddenfields.py `_ Cookies ^^^^^^^ .. literalinclude:: ../att/webprog/cgistate_cookies.py `cgistate_cookies.py `_ Screenshots: * `Das Cookie aus cgistate_cookies.py in Elinks `_ Den Zustand fälschungssicherer machen ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Screenshots: * `Man kann Cookies browserseite fälschen `_ * `Das Cookie aus cgistate_cryptcookies.py in Elinks `_ * `Vor der Fälschung des Cookies `_ * `Das Cookie wird gefälscht `_ * `Die Fälschung wird erkannt `_ URLs: * `Das Web Developer Firefox Plugin `_ * `Die pycrypto Website `_ (mit ``easy_install pycrypto`` installieren) * `A Secure Cookie Protocol by Alex X. Liu et. al. `_ (PDF) .. literalinclude:: ../att/webprog/cgistate_cookies.py `cgistate_cookies.py `_ .. literalinclude:: ../att/webprog/cryptutils.py `cryptutils.py `_ .. literalinclude:: ../att/webprog/cryptutils_demo.py `cryptutils_demo.py `_ .. literalinclude:: ../att/webprog/cryptcookie.py `cryptcookie.py `_ .. literalinclude:: ../att/webprog/cgistate_cryptcookies.py `cgistate_cryptcookies.py `_ .. literalinclude:: ../att/webprog/securecookieprotocol.py `securecookieprotocol.py `_ Den Zustand serverseitig erhalten --------------------------------- Sitzungen ^^^^^^^^^ Die grundlegende Vorgehenseweise bei Sitzungen (Pseudocode):: def handle_request(req): session = DB_Session(session_time=300) # 5 minutes = 300 secs. sessions cookie = req.getCookie('session') # decrypt and verify, else None if cookie: sess_id = cookie.getSessionID() if session.session_valid(sess_id): if req.getCommand() == LOGOUT: session.session_remove(sess_id) send_cookie(create_cookie(LOGGED_OUT)) confirmLogoutToUser() send_login_form() else: do_something_with_session_and_request(sess_id, req) session.session_extend_time(sess_id) send_some_reply() else: sendErrorToUser(INVALID_OR_EXPIRED_SESSION) send_login_form() else: if req.hasLoginCredentials(): username, password = req.loginCredentials() if verify_credentials(username, password): sess_id = session.session_create(username) send_cookie(create_cookie(sess_id)) # encrypt and sign cookie confirmLoginToUser() show_logged_in_page() else: sendErrorToUser(INVALID_USER) send_login_form() else: send_login_form() session.close() .. literalinclude:: ../att/webprog/session.py `session.py `_ Datenbankbasierte Sitzungen ^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. literalinclude:: ../att/webprog/session_dbapi.py `session_dbapi.py `_ Dateibasierte Sitzungen ^^^^^^^^^^^^^^^^^^^^^^^ Nachteile von CGI ----------------- Webclients ========== Low-level HTTP Protokoll mit httplib ------------------------------------ Einfache Webclients mit urllib ------------------------------ Flexiblere Webclients mit urllib2 --------------------------------- Eine Seite anfordern ^^^^^^^^^^^^^^^^^^^^ Eine URL herunterladen:: import urllib2 theurl = 'https://pythonbook.hajji.org/examples/net/test.html' f = urllib2.urlopen(theurl) data = f.read() f.close() Man kann auch zeilenweise iterieren:: f = urllib2.urlopen(theurl) for line in f: thelist.append(line) f.close() .. literalinclude:: ../att/webprog/fetchchunked.py `fetchchunked.py `_ Einen Teil einer Seite anfordern ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Alles, mit Ausnahme der 30 ersten Bytes laden:: import urllib2 opener = urllib2.build_opener() opener.addheaders = [('Range', 'bytes=30-'), ('User-agent', 'mybot/0.0')] op = opener.open('https://pythonbook.hajji.org/examples/net/test.html') data = op.read() Webclients mit Twisted ---------------------- .. literalinclude:: ../att/webprog/twisted_fetcher.py `twisted_fetcher.py `_ Templating Engines ================== URLs: * `Templating Module in Python `_ Templating für arme Leute ------------------------- Stringinterpolation ^^^^^^^^^^^^^^^^^^^ .. literalinclude:: ../att/webprog/template1.txt `template1.txt `_ .. literalinclude:: ../att/webprog/template2.txt `template2.txt `_ .. literalinclude:: ../att/webprog/template3.txt `template3.txt `_ string.Template ^^^^^^^^^^^^^^^ Screenshots: * `Dokumentation zum Modul string.Template `_ .. literalinclude:: ../att/webprog/template4.txt `template4.txt `_ Text-basiertes Templating ------------------------- Mako ^^^^ Screenshots: * `Website des Mako Moduls (Screenshot) `_ URLs: * `Website des Mako Moduls `_ (aber mit ``easy_install Mako`` installieren) .. literalinclude:: ../att/webprog/mako1.txt :language: mako `mako1.txt `_ .. literalinclude:: ../att/webprog/mako1.txt.py `mako1.txt.py `_ .. literalinclude:: ../att/webprog/mako_header.txt :language: mako `mako_header.txt `_ .. literalinclude:: ../att/webprog/mako_footer.txt :language: mako `mako_footer.txt `_ .. literalinclude:: ../att/webprog/mako_main.txt :language: mako `mako_main.txt `_ .. literalinclude:: ../att/webprog/mako_all.txt :language: mako `mako_all.txt `_ .. literalinclude:: ../att/webprog/showmako.py `showmako.py `_ .. literalinclude:: ../att/webprog/mako2.txt :language: mako `mako2.txt `_ .. literalinclude:: ../att/webprog/mako3.txt :language: mako `mako3.txt `_ .. literalinclude:: ../att/webprog/mako4.txt :language: mako `mako4.txt `_ .. literalinclude:: ../att/webprog/mako5.txt :language: mako `mako5.txt `_ .. literalinclude:: ../att/webprog/mako6.txt :language: mako `mako6.txt `_ .. literalinclude:: ../att/webprog/mako7.txt :language: mako `mako7.txt `_ .. literalinclude:: ../att/webprog/mako8.txt :language: mako `mako8.txt `_ .. literalinclude:: ../att/webprog/makocalc.txt :language: mako `makocalc.txt `_ .. literalinclude:: ../att/webprog/mako9.txt :language: mako `mako9.txt `_ .. literalinclude:: ../att/webprog/mako10.txt :language: mako `mako10.txt `_ .. literalinclude:: ../att/webprog/mako11.txt :language: mako `mako11.txt `_ .. literalinclude:: ../att/webprog/mako12.txt :language: mako `mako12.txt `_ .. literalinclude:: ../att/webprog/mako13.txt :language: mako `mako13.txt `_ .. literalinclude:: ../att/webprog/mako14.txt :language: mako `mako14.txt `_ .. literalinclude:: ../att/webprog/mako15.txt :language: mako `mako15.txt `_ .. literalinclude:: ../att/webprog/mako16.txt :language: mako `mako16.txt `_ .. literalinclude:: ../att/webprog/mako17.txt :language: mako `mako17.txt `_ .. mako18.txt contains umlauts, non suitable for Sphinx... `mako18.txt `_ Cheetah ^^^^^^^ URLs: * `Website von Cheetah `_ .. literalinclude:: ../att/webprog/cheetah1.txt `cheetah1.txt `_ XML-basiertes Templating ------------------------ Genshi ^^^^^^ Screenshots: * `Website von Genshi (Screenshot) `_ URLs: * `Website von Genshi `_ (aber mit ``easy_install Genshi`` installieren) .. literalinclude:: ../att/webprog/genshi1.txt :language: xml `genshi1.txt `_ .. literalinclude:: ../att/webprog/showgenshi.py `showgenshi.py `_ .. literalinclude:: ../att/webprog/genshi2.txt :language: xml `genshi2.txt `_ .. literalinclude:: ../att/webprog/genshi3.txt :language: xml `genshi3.txt `_ .. literalinclude:: ../att/webprog/genshi4.txt :language: xml `genshi4.txt `_ .. literalinclude:: ../att/webprog/genshi5.html :language: xml `genshi5.html `_ .. literalinclude:: ../att/webprog/genshi6.html :language: xml `genshi6.html `_ .. literalinclude:: ../att/webprog/genshi7.html :language: xml `genshi7.html `_ .. literalinclude:: ../att/webprog/genshi8.html :language: xml `genshi8.html `_ .. literalinclude:: ../att/webprog/genshi9.html :language: xml `genshi9.html `_ .. literalinclude:: ../att/webprog/genshi10.html :language: xml `genshi10.html `_ .. literalinclude:: ../att/webprog/genshi11.html :language: xml `genshi11.html `_ .. literalinclude:: ../att/webprog/genshi12.html :language: xml `genshi12.html `_ .. literalinclude:: ../att/webprog/genshicalc.html :language: xml `genshicalc.html `_ .. literalinclude:: ../att/webprog/genshi13.html :language: xml `genshi13.html `_ Kid ^^^ URLs: * `Website von Kid `_ Weitere Templating Systeme -------------------------- URLs: * `Nevow `_ * `SimpleTAL `_ * `Python Servlet Engine `_ * `Qpy `_ * `Evoque `_ Web Frameworks ============== Django ------ Screenshots: * `Screenshot der Django Website `_ Django installieren ^^^^^^^^^^^^^^^^^^^ URLs: * `Website von Django `_ * `Die im Buch verwendete Version `_ Ein Django-Projekt starten ^^^^^^^^^^^^^^^^^^^^^^^^^^ Screenshots: * `Die Default-Seite eines leeren Django-Projekts `_ Eine Datenbank anbinden ^^^^^^^^^^^^^^^^^^^^^^^ Modelle definieren ^^^^^^^^^^^^^^^^^^ .. literalinclude:: ../att/webprog/models.py `models.py `_ Das Modell ausprobieren ^^^^^^^^^^^^^^^^^^^^^^^ Die Admin-Site ^^^^^^^^^^^^^^ Screenshots: * `Login in die Admin Site `_ * `In die Admin Site gerade eingeloggt `_ * `Einen Artikel in der Admin-Site verändern `_ Die öffentliche Sicht ^^^^^^^^^^^^^^^^^^^^^ .. literalinclude:: ../att/webprog/urls.py `urls.py `_ Screenshots: * `Page not found `_ .. literalinclude:: ../att/webprog/views.py `views.py `_ .. literalinclude:: ../att/webprog/index.html :language: xml `index.html `_ .. literalinclude:: ../att/webprog/article.html :language: xml `article.html `_ Screenshots: * `Die öffentliche (noch nicht geskinnte) Sicht eines Artikels `_ .. literalinclude:: ../att/webprog/talkback.html :language: xml `talkback.html `_ .. literalinclude:: ../att/webprog/newtalkback.html :language: xml `newtalkback.html `_ Was noch zu berücksichtigen ist ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Weitere Web Frameworks ---------------------- URLs: * `web.py `_ * `web2.py `_ * `Webware `_ * `Pylons `_ * `Turbo Gears `_ Zope, Plone et. al. =================== Screenshots: * `Screenshot der Zope Website `_ URLs: * `Die Zope Website `_ * `Die Plone Website `_ Zope und Plone zusammen installieren ------------------------------------ Erste Schritte in Zope ---------------------- Screenshots: * `Das Zope Management Interface (ZMI) `_ * `Das Zope Template editieren `_ * `Das Demo Template editieren `_ * `Gebundene Variablen (Bindings) `_ Erzeugt man ein Script (Python), erhält man von Zope erst folgenden Anfangscode:: # Example code: # Import a standard function, and get the HTML request and response objects. from Products.PythonScripts.standard import html_quote request = container.REQUEST RESPONSE = request.RESPONSE # Return a string identifying this script. print "This is the", script.meta_type, '"%s"' % script.getId(), if script.title: print "(%s)" % html_quote(script.title), print "in", container.absolute_url() return printed Wir könnten es z.B. so verändern:: request = container.REQUEST print "We've got the following container.REQUEST object:" print request return printed Der Webrechner in Zope besteht aus einem ZPT :file:`zopecalc_pt`, das so aussieht: .. code-block:: xml The title

und aus dem dazu passenden Script (Python) :file:`zopecalc`, der davon aufgerufen wird und so aussieht:: if len(traverse_subpath) == 3: # extract arguments from PATH_INFO... op, arg1, arg2 = traverse_subpath else: # or from GET or POSt parameters. op = container.REQUEST.form.get('op', None) arg1 = container.REQUEST.form.get('arg1', None) arg2 = container.REQUEST.form.get('arg2', None) # called the first time w/o args? send form to user if op is None or arg1 is None or arg2 is None: pt = context.zopecalc_pt return pt() if op not in ('add', 'sub', 'mul', 'div'): return 'Invalid operator. Use one of add, sub, mul or div' result = 'No result yet' try: numarg1 = float(arg1) numarg2 = float(arg2) if op == 'add': result = numarg1 + numarg2 elif op == 'sub': result = numarg1 - numarg2 elif op == 'mul': result = numarg1 * numarg2 elif op == 'div': if numarg2 == 0: result = 'NaN' else: result = numarg1 / numarg2 except ValueError: return 'Invalid arguments. Use only numerical arguments.' except TypeError: return 'Invalid arguments. Missing one or both argument.' return str(result) Macros in Plone --------------- Das Script (Python) :file:`plonecalc` sieht so aus:: if len(traverse_subpath) == 3: # extract arguments from PATH_INFO... op, arg1, arg2 = traverse_subpath else: # or from GET or POSt parameters. op = container.REQUEST.form.get('op', None) arg1 = container.REQUEST.form.get('arg1', None) arg2 = container.REQUEST.form.get('arg2', None) # This will be our return template pt = context.plonecalc_pt # called the first time w/o args? send form to user if op is None or arg1 is None or arg2 is None: return pt() if op not in ('add', 'sub', 'mul', 'div'): return pt(result='Invalid operator. Use one of add, sub, mul or div') result = 'No result yet' try: numarg1 = float(arg1) numarg2 = float(arg2) if op == 'add': result = numarg1 + numarg2 elif op == 'sub': result = numarg1 - numarg2 elif op == 'mul': result = numarg1 * numarg2 elif op == 'div': if numarg2 == 0: result = 'NaN' else: result = numarg1 / numarg2 except ValueError: return pt(result='Invalid arguments. Use only numerical arguments.') except TypeError: return pt(result='Invalid arguments. Missing one or both argument.') # Now return result in a template return pt(result=str(result)) Das dazu passende Zope Page Template :file:`plone_calc_pt` lautet: .. code-block:: xml

The Plone Calculator

Previous results

The result of the previous calculation was placeholder for result .

New calculation


Screenshot: * `Der Plonerechner sieht dann so aus `_ Wikis ===== URLs: * `MoinMoin `_ * `Trac `_ Lose Enden ========== URLs: * `Python Imaging Library (PIL) `_ * `GD Bibliothek `_ * `ReportLab Toolkit zum Erzeugen von PDF-Dateien `_ Zusammenfassung ===============