=================
Werkzeug Tutorial
=================

.. admonition:: Hinweis

    Dies ist die deutsche Übersetzung des `Tutorials <tutorial.txt>`_.  Die
    Entwicklung rund um Werkzeug steht nie still, und Verbesserungen an der
    Library wirken sich oft auch auf das Tutorial aus -- deshalb ist die
    Originalversion möglicherweise aktueller.

Willkommen zum Tutorial für Werkzeug 0.2.  Wir werden einen einfachen
`TinyURL`_-Klon programmieren, der die URLs in einer Datenbank speichert.  Die
Die verwendeten Bibliotheken für diese Anwendung sind `Jinja`_ für die
Templates, `SQLAlchemy`_ für die Datenbank-Anbindung und natürlich Werkzeug
für WSGI.

Wir haben uns hier für diese Komponenten entschieden, weil wir einen
`Django`_-ähnlichen Grundaufbau nachstellen wollen.  Dazu zählen wir zum
Beispiel View-Funktionen anstelle der in `Rails`_ und `Pylons`_ gängigen
Controller-Klassen mit Action-Methoden, sowie designerfreundliche Templates.

In Werkzeugs `Beispiel-Ordner`_ befinden sich einige Anwendungen, die andere
Konzepte verfolgen, Template-Engines einsetzen etc.  Dort liegt auch der
Quellcode der Anwendung, die wir in diesem Tutorial erstellen werden.

Du kannst `easy_install`_ verwenden, um Jinja und SQLAlchemy zu installieren,
falls diese nicht bereits installiert sind::

    sudo easy_install Jinja
    sudo easy_install SQLAlchemy

Diese Befehle funktionieren auch auf einem Windows-System (mit
Administratorrechten), sofern die `setuptools` installiert sind, allerdings
musst du das `sudo` weglassen.  Als OS X-Benutzer könntest du die Libraries
auch via port installieren, Linux-Benutzer finden diese Pakete möglicherweise
auch in ihrem Paketmanager.

Wenn du neugierig bist, kannst du dir auch die `Online-Demo`_ der Anwendung
ansehen.

Noch ein kleiner Hinweis: Dieses Tutorial erfordert Python 2.4.

.. _TinyURL: http://tinyurl.com/
.. _Django: http://www.djangoproject.com/
.. _Jinja: http://jinja.pocoo.org/
.. _SQLAlchemy: http://sqlalchemy.org/
.. _Rails: http://www.rubyonrails.org/
.. _Pylons: http://pylonshq.com/
.. _Beispiel-Ordner: http://dev.pocoo.org/projects/werkzeug/browser/examples
.. _easy_install: http://peak.telecommunity.com/DevCenter/EasyInstall
.. _setuptools: http://pypi.python.org/pypi/setuptools
.. _Online-Demo: http://werkzeug.pocoo.org/e/shorty/


Teil 0: Die Ordnerstruktur
==========================

Bevor wir beginnen können, müssen wir ein Python-Paket für unsere
Werkzeug-Anwendung erstellen.  Dort werden wir die Anwendung, die Templates
und die statischen Dateien ablegen.

Die Anwendung dieses Tutorials nennen wir `shorty` und die Struktur für unsere
Anwendung sieht etwa so aus::

    manage.py
    shorty/
        __init__.py
        static/
        templates/

Die Dateien ``__init__.py`` und ``manage.py`` lassen wir für den Moment einmal
leer.  Die erste dieser Dateien macht aus dem Ordner ``shorty`` ein
Python-Paket, die zweite werden wir später für unsere Verwaltungsfunktionen
nutzen.


Teil 1: Die WSGI-Anwendung
==========================

Im Gegensatz zu Django oder ähnlichen Frameworks arbeitet Werkzeug direkt auf
der WSGI-Schicht.  Es gibt keine schicke Magie, die die zentrale WSGI-Anwendung
für uns implementiert.  Das bedeutet, dass wir als Erstes eben diese
programmieren müssen.  Eine WSGI-Anwendung ist eine Funktion oder, noch besser,
eine Klasse mit einer Methode ``__call__``.

Eine aufrufbare Klasse hat große Vorteile gegenüber einer Funktion: Zum Einen
kann man Konfigurationsparameter direkt an den Konstruktor übergeben, zum
Anderen können wir WSGI-Middlewares innerhalb der WSGI-Anwendung hinzufügen.
Das ist nützlich für Middlewares, die entscheidend für die Funktion der
Anwendung sind (z.B. eine Session-Middleware).

Hier zunächst einmal der Quellcode für unsere Datei ``shorty/application.py``,
in der wir die WSGI-Anwendung ablegen::

    from sqlalchemy import create_engine
    from werkzeug import Request, ClosingIterator
    from werkzeug.exceptions import HTTPException

    from shorty.utils import session, metadata, local, local_manager, url_map
    from shorty import views
    import shorty.models


    class Shorty(object):

        def __init__(self, db_uri):
            local.application = self
            self.database_engine = create_engine(db_uri, convert_unicode=True)

        def init_database(self):
            metadata.create_all(self.database_engine)

        def __call__(self, environ, start_response):
            local.application = self
            request = Request(environ)
            local.url_adapter = adapter = url_map.bind_to_environ(environ)
            try:
                endpoint, values = adapter.match()
                handler = getattr(views, endpoint)
                response = handler(request, **values)
            except HTTPException, e:
                response = e
            return ClosingIterator(response(environ, start_response),
                                   [session.remove, local_manager.cleanup])

Ziemlich viel für Code für den Anfang ... gehen wir ihn mal Schritt für Schritt
durch.  Zunächst sehen wir einige Imports: Aus dem Paket `sqlalchemy` holen wir
uns eine Factory-Funktion, die eine neue Datenbank-Engine für uns erstellt, die
wiederum einen Connection-Pool bereithält.  Die nächsten Imports holen einige
Objekte in den Namensraum, die uns Werkzeug zur Verfügung stellt: ein
Request-Objekt; ein spezieller Iterator, der uns hilft am Ende eines Requests
einige Dinge aufzuräumen; und schließlich die Basisklasse für alle
HTTP-Exceptions.

Die nächsten fünf Imports funktionieren noch nicht, weil wir das `utils`-Modul
noch nicht erstellt haben.  Doch wir werden trotzdem schon ein wenig über diese
Objekte sprechen.  Das Objekt `session` ist kein aus PHP bekanntes
Session-Array, sondern eine Datenbank-Session von SQLAlchemy.  Alle
Datenbank-Models, die im Kontext eines Requests erstellt werden, sind auf
diesem Objekt zwischengespeichert, so dass man Änderungen mit einem Schlag zum
Server senden kann.  Im Gegensatz zu Django wird ein instantiiertes
Datenbank-Model automatisch von der Session verwaltet und ist in dieser Session
ein Singleton.  Es kann also niemals zwei Instanzen des selben
Datenbankeintrags in einer Session geben.  Das Objekt `metadata` stammt
ebenfalls aus SQLAlchemy und speichert Informationen über die Tabellen der
Datenbank.  Es stellt zum Beispiel eine Funktion bereit, die alle im Model
definierten Tabellen in der Datenbank erstellt.

Das Objekt `local` ist ein kontext-lokales Objekt, erstellt vom Modul `utility`.
Attributzugriffe auf dieses Objekt sind an den aktuellen Request gebunden,
d.h. jeder Request bekommt ein anderes Objekt zurück und kann verschiedene
Objekte ablegen, ohne Threadingprobleme zu bekommen.  Der `local_manager` wird
genutzt, um am Ende des Requests alle auf dem `local`-Objekt gespeicherten Daten
wieder freizugeben.

Der letzte Import von dort ist die URL-Map, die alle URL-Routen verwaltet.
Solltest du bereits mit Django gearbeitet haben, ist dies vergleichbar mit den
regulären Ausdrücken in der jeweiligen ``urls.py``.  Kennst du PHP, ist die
URL-Map ähnlich einem eingebauten `mod_rewrite`.

Zusätzlich importieren wir hier unser `views`-Modul, das die View-Funktionen
enthält, sowie das `models`-Modul, in welchem unsere Models definiert sind.
Auch wenn es so aussieht, als ob wir diesen Import nicht nutzen, ist er
wichtig.  Nur dadurch werden unsere Tabellen auf dem `metadata`-Objekt
registriert.

Schauen wir auf die Anwendungsklasse.  Der Konstruktor dieser Klasse nimmt die
Datenbank-URI entgegen, die -- einfach gesagt -- den Typ der Datenbank und die
Verbindungsdaten enthält.  Für SQLite das wäre zum Beispiel
``sqlite:////tmp/shorty.db`` (die vier Slashes sind **kein** Tippfehler).

Im Konstruktor erstellen wir auch gleich eine Datenbank-Engine für diese URI und
aktivieren das automatische Umwandeln von Bytestrings nach Unicode.  Das ist
nützlich, weil sowohl Jinja als auch Werkzeug intern nur Unicode verwenden.

Des Weiteren binden wir die Anwendung an das `local`-Objekt.  Das ist
eigentlich nicht nötig, aber nützlich, wenn wir mit der Anwendung in der
Python-Shell spielen wollen.  Damit werden direkt nach dem Instantiieren der
Anwendung die Datenbankfunktionen testen.  Wenn wir das nicht tun, wird der
Python-Interpreter einen Fehler werfen, wenn außerhalb eines Requests versucht
wird, eine SQLAlchemy-Session zu erstellen.

Die Methode `init_database` können wir später im Managementscript verwenden, um
alle Tabellen zu erstellen, die wir definiert haben.

Nun zur eigentlichen WSGI-Anwendung, der `__call__`-Methode.  Dort passiert das
so genannte "Request Dispatching", also das Weiterleiten von eingehenden
Anfragen zu den richtigen Funktionen.  Als Erstes erstellen wir dort ein neues
Request-Objekt, um nicht direkt mit `environ`, dem Dictionary mit den
Umgebungsvariablen, arbeiten zu müssen.  Dann binden wir die Anwendung an das
`local`-Objekt für den aktuellen Kontext.

Anschließend erstellen wir einen URL-Adapter, indem wir die URL-Map an die
aktuelle WSGI-Umgebung binden.  Der Adapter weiß dann, wie die aktuelle URL
aussieht, wo die Anwendung eingebunden ist etc.  Diesen Adapter können wir
nutzen, um URLs zu erzeugen oder gegen den aktuellen Request zu matchen.  Wir
binden diesen Adapter auch an das `local`-Objekt, damit wir im `utils`-Modul
auf ihn zugreifen können.

Danach kommt ein `try`/`except`-Konstrukt, das HTTP-Fehler abfängt, die während
des Matchings oder in einer View-Funktion auftreten können.  Wenn der Adapter
keinen Endpoint für die aktuelle URL findet, wird er eine `NotFound`-Exception
werfen, die wir wie ein Response Objekt aufrufen können.  Der Endpoint ist in
unserem Fall der Name der Funktion im `views`-Modul, die wir aufrufen möchten.
Wir suchen uns einfach mit `getattr` die Funktion dem Namen nach heraus und
rufen sie mit dem Request-Objekt und den URL-Werten auf.

Am Schluss rufen wir das gewonnene Response-Objekt (oder die Exception) als
WSGI-Anwendung auf und übergeben den Rückgabewert dieser Funktion an den
`ClosingIterator`, zusammen mit zwei Funktionen fürs Aufräumen.  Dies schließt
die SQLAlchemy-Session und leert das `local`-Objekt für diesen Request.

Nun müssen wir zwei leere Dateien ``shorty/views.py`` und ``shorty/models.py``
erstellen, damit die Imports nicht fehlschlagen.  Den tatsächlichen Code für
diese Module werden wir ein wenig später erstellen.


Teil 2: Die Utilities
=====================

Nun haben wir die eigentliche WSGI-Applikation fertig gestellt, aber wir müssen
das Utility-Modul noch um Code ergänzen, damit die Imports klappen.  Fürs Erste
fügen wir nur die Objekte hinzu, die wir brauchen, damit die Applikation
funktioniert.  Der folgende Code landet in der Datei ``shorty/utils.py``:

.. sourcecode:: python

    from sqlalchemy import MetaData
    from sqlalchemy.orm import create_session, scoped_session
    from werkzeug import Local, LocalManager
    from werkzeug.routing import Map, Rule

    local = Local()
    local_manager = LocalManager([local])
    application = local('application')

    metadata = MetaData()
    session = scoped_session(lambda: create_session(application.database_engine,
                             transactional=True), local_manager.get_ident)

    url_map = Map()
    def expose(rule, **kw):
        def decorate(f):
            kw['endpoint'] = f.__name__
            url_map.add(Rule(rule, **kw))
            return f
        return decorate

    def url_for(endpoint, _external=False, **values):
        return local.url_adapter.build(endpoint, values, force_external=_external)

Zunächst importieren wir wieder eine Menge, dann erstellen wir das
`local`-Objekt und den Manager dafür, wie bereits im vorherigen Abschnitt
besprochen.  Neu ist hier, dass der Aufruf eines `local`-Objekts mit einem
String ein Proxy-Objekt zurück gibt.  Dieses zeigt stets auf die gleichnamigen
Attribute des `local`-Objekts.  Beispielsweise verweist nun `application`
dauerhaft auf `local.application`.  Wenn du jedoch darauf zugreifst und kein
Objekt an `local.application` gebunden ist, erhältst du einen `RuntimeError`.

Die folgenden drei Zeilen sind im Prinzip alles, um SQLAlchemy 0.4 oder höher in
eine Werkzeug-Anwendung einzubinden.  Wir erstellen ein Metadaten-Objekt für all
unsere Tabellen sowie eine "scoped session" über die
`scoped_session`-Factory-Funktion.  Dadurch wird SQLAlchemy angewiesen,
praktisch denselben Algorithmus zur Ermittlung des aktuellen Kontextes zu
verwenden, wie es auch Werkzeug für die `local`-Objekte tut, und die
Datenbank-Engine der aktuellen Applikation zu benutzen.

Wenn wir nicht vorhaben, mehrere Instanzen der Applikation in derselben Instanz
des Python-Interpreters zu unterstützen, können wir den Code einfach halten,
indem wir nicht über das aktuelle `local`-Objekt auf die Applikation zugreifen,
sondern einen anderen Weg nehmen.  Dieser Ansatz wird etwa von Django verfolgt,
macht es allerdings unmöglich, mehrere solcher Applikationen zu kombinieren.

Der restliche Code des Moduls wird für unsere Views benutzt.  Die Idee besteht
darin, Dekoratoren zu benutzen, um die URL-Dispatching-Regeln für
View-Funktionen festzulegen, anstatt ein zentrales Modul ``urls.py`` zu
verwenden, wie es Django tut, oder über eine ``.htaccess``-Datei URLs
umzuschreiben, wie man es in PHP machen würde.  Dies ist **eine** Möglichkeit,
dies zu tun, und es gibt unzählige andere Wege der Handhabung von
URL-Regeldefinitionen.

Die Funktion `url_for`, die wir ebenfalls definieren, bietet einen einfachen
Weg, URLs anhand des Endpointes zu generieren.  Wir werden sie später in den
Views als auch unserem Model verwenden.


Unterbrechung: Und nun etwas komplett anderes
=============================================

Da wir nun das Grundgerüst für unsere Anwendung fertig gestellt haben, können
wir jetzt erst einmal relaxen und uns etwas komplett anderem zuwenden: den
Verwaltungs-Scripts.  Während der Entwicklung erledigt man häufig immer
wiederkehrende Aufgaben, wie zum Beispiel das Starten eines Entwicklungs-Servers
(im Gegensatz zu PHP benötigt Werkzeug keinen Apache-Server; der in Python
integrierte `wsgiref`-Server ist völlig ausreichend und für die Entwicklung auf
jeden Fall empfehlenswert), das Starten eines Python-Interpreters (um mit den
Datenbankobjekten herumzuspielen oder die Datenbank zu initialisieren) etc.

Werkzeug macht es unglaublich einfach, solche Verwaltungs-Scripts zu schreiben.
Der folgende Code implementiert ein voll funktionsfähiges Verwaltungs-Script
und gehört in die `manage.py`-Datei, welche du am Anfang erstellt hast:

.. sourcecode:: python

    #!/usr/bin/env python
    from werkzeug import script

    def make_app():
        from shorty.application import Shorty
        return Shorty('sqlite:////tmp/shorty.db')

    def make_shell():
        from shorty import models, utils
        application = make_app()
        return locals()

    action_runserver = script.make_runserver(make_app, use_reloader=True)
    action_shell = script.make_shell(make_shell)
    action_initdb = lambda: make_app().init_database()

    script.run()

`werkzeug.script` ist genauer in der `Script-Dokumentation`_ beschrieben, und da
der Großteil des Codes verständlich sein sollte, werden wir hier nicht näher
darauf eingehen.

Es ist aber wichtig, dass du ``python manage.py shell`` ausführen kannst, um
eine interaktive Python-Shell zu starten.  Solltest du einen Traceback
bekommen, kontrolliere bitte die darin genannte Code-Zeile und vergleiche sie
mit dem entsprechenden Code in dieser Anleitung.

Sobald das Script läuft, können wir mit dem Schreiben der Datenbank-Models
beginnen.

.. _Script-Dokumentation: script.txt


Teil 3: Datenbank-Models
========================

Jetzt können wir die Models erstellen.  Da die Anwendung ziemlich einfach ist,
haben wir nur ein Model und eine Tabelle:

.. sourcecode:: python

    from datetime import datetime
    from sqlalchemy import Table, Column, String, Boolean, DateTime
    from shorty.utils import session, metadata, url_for, get_random_uid

    url_table = Table('urls', metadata,
        Column('uid', String(140), primary_key=True),
        Column('target', String(500)),
        Column('added', DateTime),
        Column('public', Boolean)
    )

    class URL(object):

        def __init__(self, target, public=True, uid=None, added=None):
            self.target = target
            self.public = public
            self.added = added or datetime.utcnow()
            if not uid:
                while True:
                    uid = get_random_uid()
                    if not URL.query.get(uid):
                        break
            self.uid = uid

        @property
        def short_url(self):
            return url_for('link', uid=self.uid, _external=True)

        def __repr__(self):
            return '<URL %r>' % self.uid

    session.mapper(URL, url_table)


Dieses Modul ist gut überschaubar.  Wir importieren alles, was wir von
SQLAlchemy benötigen, und erstellen die Tabelle.  Dann fügen wir eine Klasse
für diese Tabelle hinzu und verbinden beide miteinander.  Für eine
detailliertere Erklärung bezüglich SQLAlchemy solltest du dir das
`exzellente Tutorial`_ anschauen.

Im Konstruktor generieren wir solange eine eindeutige ID, bis wir eine finden,
die noch nicht belegt ist.  Die `get_random_uid`-Funktion fehlt -- wir müssen
sie noch in unser `utils`-Modul einfügen:

.. sourcecode:: python

    from random import sample, randrange

    URL_CHARS = 'abcdefghijkmpqrstuvwxyzABCDEFGHIJKLMNPQRST23456789'

    def get_random_uid():
        return ''.join(sample(URL_CHARS, randrange(3, 9)))

Wenn das getan ist, können wir ``python manage.py initdb`` ausführen, um die
Datenbank zu erstellen und ``python manage.py shell``, um damit herumzuspielen:

.. sourcecode:: pycon

    Interactive Werkzeug Shell
    >>> from shorty.models import session, URL

Jetzt können wir einige URLs zu der Datenbank hinzufügen:

.. sourcecode:: pycon

    >>> urls = [URL('http://example.org/'), URL('http://localhost:5000/')]
    >>> URL.query.all()
    []
    >>> session.commit()
    >>> URL.query.all()
    [<URL '5cFbsk'>, <URL 'mpugsT'>]

Wie du sehen kannst, müssen wir ``session.commit()`` aufrufen, um die Änderungen
in der Datenbank zu speichern.  Nun erstellen wir ein privates Element mit einer
eigenen UID:

.. sourcecode:: pycon

    >>> URL('http://werkzeug.pocoo.org/', False, 'werkzeug-webpage')
    >>> session.commit()

Dann fragen wir alle ab:

.. sourcecode:: pycon

    >>> URL.query.filter_by(public=False).all()
    [<URL 'werkzeug-webpage'>]
    >>> URL.query.filter_by(public=True).all()
    [<URL '5cFbsk'>, <URL 'mpugsT'>]
    >>> URL.query.get('werkzeug-webpage')
    <URL 'werkzeug-webpage'>

Jetzt haben wir einige Datensätze in der Datenbank und wissen ungefähr, auf
welche Weise SQLAlchemy funktioniert.  Zeit, unsere Views zu erstellen.

.. _exzellente Tutorial: http://www.sqlalchemy.org/docs/04/ormtutorial.html


Teil 4: Die View-Funktionen
===========================

Nachdem wir mit SQLAlchemy herumgespielt haben, können wir zurück zu Werkzeug
gehen und anfangen, unsere View-Funktionen zu erstellen.  Der Begriff
"View-Funktion" kommt von Django. Dort werden die Funktionen, die Templates
befüllen und ausgeben, so genannt.  Deshalb ist unser Beispiel eine Umsetzung
von MVT (Model, View, Template) und nicht etwa MVC (Model, View, Controller).
Die beiden Bezeichnungen bedeuten dasselbe, aber es ist viel einfacher,
dieselbe Benennung wie Django zu nutzen.

Als Anfang erstellen wir einfach eine View-Funktion für neue URLs und eine
Funktion, die eine Nachricht über einen neuen Link darstellt.  Das wird der
Inhalt unserer noch leeren ``views.py``-Datei:

.. sourcecode:: python

    from werkzeug import redirect
    from werkzeug.exceptions import NotFound
    from shorty.utils import session, render_template, expose, validate_url, \
         url_for
    from shorty.models import URL

    @expose('/')
    def new(request):
        error = url = ''
        if request.method == 'POST':
            url = request.form.get('url')
            alias = request.form.get('alias')
            if not validate_url(url):
                error = u"Entschuldigung, aber ich kann die angegebene " \
                        u"URL nicht kürzen."    
            elif alias:
                if len(alias) > 140:
                    error = 'Dein Alias ist zu lang'
                elif '/' in alias:
                    error = 'Dein Alias darf keinen Slash beinhalten'
                elif URL.query.get(alias):
                    error = 'Der angegeben Alias existiert bereits'
            if not error:
                uid = URL(url, 'private' not in request.form, alias).uid
                session.commit()
                return redirect(url_for('display', uid=uid))
        return render_template('new.html', error=error, url=url)

    @expose('/display/<uid>')
    def display(request, uid):
        url = URL.query.get(uid)
        if not url:
            raise NotFound()
        return render_template('display.html', url=url)

    @expose('/u/<uid>')
    def link(request, uid):
        url = URL.query.get(uid)
        if not url:
            raise NotFound()
        return redirect(url.target, 301)

    @expose('/list/', defaults={'page': 1})
    @expose('/list/<int:page>')
    def list(request, page):
        pass

Wieder einmal ziemlich viel Code, aber das meiste ist normale
Formularvalidierung.  Wir erstellen zwei Funktionen, `new` und `display`, und
dekorieren sie mit unserem `expose`-Dekorator aus dem `utils`-Modul.  Dieser
Dekorator fügt eine neue Regel zur URL-Map hinzu, indem er alle Parameter zum
Konstruktor eines Rule-Objekts übergibt und den Endpoint auf den Namen der
Funktion setzt.  Damit können wir einfach URLs zu den Funktionen erzeugen --
wir nutzen ihre Funktionsnamen als Endpoint.

Denke daran, dass dieser Code nicht unbedingt eine gute Idee für größere
Anwendungen ist.  In solchen Fällen ist es besser, den vollen Importnamen mit
einem allgemeinen Prefix oder etwas Ähnlichem als Endpoint zu nutzen.  Sonst
wird es ziemlich verwirrend.

Die Formularvalidierung in der `new`-Methode ist ziemlich simpel.  Wir
kontrollieren, ob die aktuelle HTTP-Methode `POST` ist.  Falls ja nehmen wir
die Daten vom Request und validieren sie.  Wenn dort kein Fehler auftritt,
erstellen wir ein neues `URL`-Objekt, übergeben es der Datenbank und leiten auf
die Anzeige-Seite um.

Die `display`-Funktion ist nicht viel komplizierter.  Die URL-Rule erwartet
einen `uid`-Parameter, welchen die Funktion entsprechend akzeptiert.  Danach
holen wir das `URL`-Objekt mit der angegebenen UID und geben das Template aus,
dem wir wiederum das `URL`-Objekt übergeben.

Wenn die URL nicht existiert, werfen wir eine `NotFound`-Exception, welche eine
statische "404 Seite nicht gefunden"-Seite anzeigt.  Wir können diese später
mit einer speziellen Fehlerseite ersetzen, indem wir die Exception auffangen,
bevor die `HTTPException` geworfen wird.

Die View-Funktion `link` wird von unseren Models in der `short_url`-Eigenschaft
genutzt und ist die kurze URL, die wir vermitteln.  Wenn also die URL-UID
``foobar`` ist, wird die URL unter ``http://localhost:5000/u/foobar``
erreichbar sein.

Die View-Funktion `list` wurde noch nicht geschrieben, das machen wir später.
Wichtig ist allerdings, dass die Funktion einen optionalen URL-Parameter
akzeptiert.  Der erste Dekorator sagt Werkzeug, dass für den Request-Pfad
``/page/`` die erste Seite angezeigt wird (da der Parameter `page`
standardmäßig auf 1 gesetzt wird).  Wichtiger ist die Tatsache, dass Werkzeug
URLs normalisiert.  Wenn du also ``/page`` oder ``/page/1`` aufrufst, wirst du
in beiden Fällen zu ``/page/`` umgeleitet.  Das geschieht automatisch und macht
Google glücklich.  Wenn du dieses Verhalten nicht magst, kannst du es
abschalten.

Und wieder einmal müssen wir zwei Objekte aus dem `utils`-Modul importieren,
die jetzt noch nicht existieren.  Eines von diesen soll ein Jinja-Template in
ein Response-Objekt verwandeln, das andere prüft eine URL.  Fügen wir also
diese zu ``utils.py`` hinzu:

.. sourcecode:: python

    from os import path    
    from urlparse import urlparse
    from werkzeug import Response
    from jinja import Environment, FileSystemLoader

    ALLOWED_SCHEMES = frozenset(['http', 'https', 'ftp', 'ftps'])
    TEMPLATE_PATH = path.join(path.dirname(__file__), 'templates')
    jinja_env = Environment(loader=FileSystemLoader(TEMPLATE_PATH))
    jinja_env.globals['url_for'] = url_for

    def render_template(template, **context):
        return Response(jinja_env.get_template(template).render(**context),
                        mimetype='text/html')

    def validate_url(url):
        return urlparse(url)[0] in ALLOWED_SCHEMES

Im Grunde ist das alles.  Die Validierungsfunktion prüft, ob deine URL wie eine
HTTP- oder FTP-URL aussieht.  Wir machen dies, um uns zu versichern, dass
niemand potentiell gefährliches JavaScript oder ähnliche URLs abschickt.  Die
`render_template`-Funktion ist auch nicht viel komplizierter, sie schaut nach
einem Template im Dateisystem im `templates`-Ordner und gibt es als Response
aus.

Weiterhin fürgen wir die `url_for`-Funktion in den globalen Kontext des
Templates ein, so dass wir auch in Templates URLs erzeugen können.

Da wir jetzt unsere beiden ersten View-Funktionen haben, ist es Zeit, die
Templates hinzuzufügen.


Teil 5: Die Templates
=====================

Wir haben beschlossen, in diesem Beispiel Jinja-Templates zu nutzen.  Wenn du
weißt, wie man Django-Templates nutzt, sollte es dir bekannt vorkommen; wenn du
bis jetzt mit PHP gearbeitet hast, kannst du Jinja-Templates mit Smarty
vergleichen.  Wenn du bis jetzt PHP als Templatesprache genutzt hast, solltest
du für dein nächstes Projekt mal `Mako`_ ansehen.

**Sicherheitswarnung**: Wir nutzen hier Jinja, welches eine textbasierte
Template-Engine ist.  Da Jinja nicht weiß, womit es arbeitet, musst du, wenn du
HTML-Templates erstellst, *alle* Werte maskieren, die irgendwann an
irgendeinem Punkt irgendeines der folgenden Zeichen enthalten können: ``>``,
``<`` oder ``&``.  Innerhalb von Attributen musst du außerdem Anführungszeichen
maskieren.  Du kannst Jinjas ``|e``-Filter für normales Escaping benutzen.
Wenn du `true` als Argument übergibst, maskiert es außerdem Anführungszeichen
(``|e(true)``).  Wie du in den Beispielen unterhalb sehen kannst, maskieren wir
die URLs nicht.  Der Grund dafür ist, dass wir keine ``&`` in den URLs haben
und deshalb ist es sicher, auf Escaping zu verzichten.

Der Einfachheit halber werden wir HTML 4 in unseren Templates nutzen.  Wenn du
etwas Erfahrung mit XHTML hast, kannst du sie in XHTML schreiben.  Aber beachte,
dass das Beispiel-Stylesheet unten nicht mit XHTML funktioniert.

Eine coole Sache, die Jinja von Django übernommen hat, ist Templatevererbung.
Das bedeutet, dass wir oft genutzte Stücke in ein Basistemplate auslagern und
es mit Platzhaltern füllen können.  Beispielsweise landen der Doctype und der
HTML-Rahmen in der Datei ``templates/layout.html``:

.. sourcecode:: html+jinja

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
     "http://www.w3.org/TR/html4/strict.dtd">
    <html>
    <head>
      <title>Shorty</title>
    </head>
    <body>
      <h1><a href="{{ url_for('new') }}">Shorty</a></h1>
      <div class="body">{% block body %}{% endblock %}</div>
      <div class="footer">
        <a href="{{ url_for('new') }}">Neu</a> |
        <a href="{{ url_for('list') }}">Liste</a> |
        Benutze Shorty für Gutes, nicht für Böses
      </div>
    </body>
    </html>

Von diesem Template können wir in unserer ``templates/new.html`` erben:

.. sourcecode:: html+jinja

    {% extends 'layout.html' %}
    {% block body %}
      <h2>Erstelle eine Shorty-URL!</h2>
      {% if error %}<div class="error">{{ error }}</div>{% endif -%}
      <form action="" method="post">
        <p>Gebe die URL an, die du kürzen willst</p>
        <p><input type="text" name="url" id="url" value="{{ url|e(true) }}"></p>
        <p>Optional kannst du der URL einen merkbaren Namen geben</p>
        <p><input type="text" id="alias" name="alias">{#
         #}<input type="submit" id="submit" value="Mach!"></p>
        <p><input type="checkbox" name="private" id="private">
           <label for="private">mache diese URL privat, also zeige sie
             nicht auf der Liste</label></p>
      </form>
    {% endblock %}

Wenn du dich über den Kommentar zwischen den beiden `input`-Elementen wunderst,
das ist ein sauberer Trick, die Templates sauber zu halten ohne Leerzeichen
zwischen beide Elemente zu setzen.  Wir haben ein Stylesheet vorbereitet,
welches dort keine Leerzeichen erwartet.

Ein zweites Template für die Anzeige-Seite (``templates/display.html``):

.. sourcecode:: html+jinja

    {% extends 'layout.html' %}
    {% block body %}
      <h2>Verkürzte URL</h2>
      <p>
        Die URL {{ url.target|urlize(40, true) }}
        wurde gekürzt zu {{ url.short_url|urlize }}.
      </p>
    {% endblock %}

Jinjas `urlize`-Filter übersetzt eine URL in einem Text in einen klickbaren
Link.  Wenn du ihm einen Integer übergibst, wird er den angezeigten Text auf
diese Anzahl Zeichen kürzen; wenn du `true` als zweiten Parameter übergibst,
wird ein `nofollow`-Flag hinzugefügt.

Da wir jetzt unsere ersten beiden Templates fertig haben, ist es Zeit, den
Server zu starten und auf den Teil der Anwendung zu schauen, der bereits
funktioniert: neue URLs hinzufügen und weitergeleitet werden.


Zwischenschritt: Das Design hinzufügen
======================================

Jetzt ist es Zeit, etwas anderes zu machen: ein Design hinzufügen.
Designelemente sind normalerweise in statischen CSS-Stylesheets definiert.
Also müssen wir einige statische Dateien irgendwo ablegen -- aber das ist ein
wenig kompliziert.  Wenn du mit bis jetzt mit PHP gearbeitet hast, wirst du
gemerkt haben, dass es hier nichts gibt, was eine URL zum Dateisystempfad
übersetzt und so direkt auf statische Dateien zugreift.  Du musst dem Webserver
oder unserem Entwicklungsserver explizit sagen, dass es einen Pfad gibt, der
die statischen Dateien beinhaltet.

Django empfiehlt eine separate Subdomain und einen eigenen Server für die
statischen Dateien, was eine sehr gute Idee für Umgebungen mit hoher Serverlast
ist, aber zu viel des Guten für diese simple Anwendung.

Hier also folgendes Vorgehen: Wir lassen unsere Anwendung die statischen Dateien
ausliefern, aber im Produktionsmodus solltest du dem Apachen mitteilen, dass er
diese Dateien selbst ausliefern soll.  Das geschieht mit Hilfe der
`Alias`-Direktive in der Konfiguration von Apache:

.. sourcecode:: apache

    Alias /static /path/to/static/files

Das ist um einiges schneller.

Und wie sagen wir unserer Anwendung, dass sie den Ordner mit statischen Dateien
als ``/static`` verfügbar machen soll?  Glücklicherweise ist das ziemlich
einfach, da Werkzeug dafür eine WSGI-Middleware liefert.  Es gibt zwei
Möglichkeiten, diese zu integrieren: Entweder wrappen wir die ganze Anwendung
in diese Middleware (diesen Weg empfehlen wir wirklich nicht) oder wir wrappen
nur die Ausführungsfunktion (viel besser, weil wir die Referenz auf das
Anwendungsobjekt nicht verlieren).  Also gehen wir zurück zur
``application.py`` und passen den Code ein wenig an.

Als Erstes musst du einen neuen Import hinzufügen und den Pfad zu den
statischen Dateien ermitteln:

.. sourcecode:: python

    from os import path
    from werkzeug import SharedDataMiddleware

    STATIC_PATH = path.join(path.dirname(__file__), 'static')

Es wäre besser, die Pfad-Manipulation in die ``utils.py``-Datei zu verschieben,
weil wir den Template-Pfad bereits dort ermittelt haben.  Aber das ist nicht
wirklich von Interesse, und wegen der Einfachheit können wir es im
Anwendungsmodul lassen.

Wie können wir also die Ausführungsfunktion wrappen?  Theoretisch müssen wir
einfach ``self.__call__ = wrap(self.__call__)`` schreiben, doch leider klappt
das so nicht in Python.  Es ist aber nicht viel schwieriger:  Benenne einfach
`__call__` in `dispatch` um und füge eine neue `__call__`-Methode hinzu:

.. sourcecode:: python

        def __call__(self, environ, start_response):
            return self.dispatch(environ, start_response)

Jetzt können wir in unsere `__init__`-Funktion gehen und die Middleware
zuschalten, indem wir die `dispatch`-Methode einschieben:

.. sourcecode:: python

        self.dispatch = SharedDataMiddleware(self.dispatch, {
            '/static':  STATIC_PATH
        })

Das war jetzt nicht schwer.  Mit diesem Weg können wir WSGI-Middlewares in der
Anwendungsklasse einhaken!

Eine andere gute Idee ist es, unserer `url_map` im `utils`-Modul den Ort
unserer statischen Dateien mitzuteilen, indem wir eine Regel hinzufügen.  Auf
diesem Weg können wir URLs zu den statischen Dateien in den Templates
generieren:

.. sourcecode:: python

    url_map = Map([Rule('/static/<file>', endpoint='static', build_only=True)])

Jetzt können wir unsere ``templates/layout.html``-Datei wieder öffnen und einen
Link zum ``style.css``-Stylesheet hinzufügen, welches wir danach erstellen
werden:

.. sourcecode:: html+jinja

    <link rel="stylesheet" type="text/css" href="{{ url_for('static',
      file='style.css') }}">

Das geht natürlich in den `<head>`-Tag, wo zur Zeit nur der Titel festgelegt
ist.

Du kannst jetzt ein nettes Layout gestalten oder das `Beispiel-Stylesheet`_
nutzen.  In beiden Fällen musst du es in die Datei ``static/style.css``
einfügen.

.. _Mako: http://www.makotemplates.org/
.. _Beispiel-Stylesheet: http://dev.pocoo.org/projects/werkzeug/browser/examples/shorty/static/style.css


Teil 6: Öffentliche URLs auflisten
==================================

Jetzt wollen wir alle öffentlichen URLs auf der "List"-Seite auflisten.  Das
sollte kein großes Problem sein, aber wir wollen auch eine Art Seitenumbruch
haben.  Da wir alle URLs auf einmal ausgeben, haben wir früher oder später
eine endlose Seite, die Minuten zum Laden benötigt.

Beginnen wir also mit dem Hinzufügen einer `Pagination`-Klasse zu unserem
`utils`-Modul:

.. sourcecode:: python

    from werkzeug import cached_property

    class Pagination(object):

        def __init__(self, query, per_page, page, endpoint):
            self.query = query
            self.per_page = per_page
            self.page = page
            self.endpoint = endpoint

        @cached_property
        def count(self):
            return self.query.count()

        @cached_property
        def entries(self):
            return self.query.offset((self.page - 1) * self.per_page) \
                             .limit(self.per_page).all()

        has_previous = property(lambda x: x.page > 1)
        has_next = property(lambda x: x.page < x.pages)
        previous = property(lambda x: url_for(x.endpoint, page=x.page - 1))
        next = property(lambda x: url_for(x.endpoint, page=x.page + 1))
        pages = property(lambda x: max(0, x.count - 1) // x.per_page + 1)

Dies ist eine sehr einfache Klasse, die das meiste der Seitenumbrüche für uns
übernimmt.  Wir können ihr eine unausgeführte SQLAlchemy-Abfrage (Query)
übergeben, die Anzahl der Elemente pro Seite, die aktuelle Seite und den
Endpoint, welcher für die URL-Generation benutzt wird.  Der
`cached_property`-Dekorator funktioniert, wie du siehst, fast genau so wie der
normale `property`-Dekorator, mit der Ausnahme, dass er sich das Ergebnis merkt.
Wir werden diese Klasse nicht genau besprechen, aber das Grundprinzip ist, dass
ein Zugriff auf `pagination.entries` die Elemente für die aktuelle Seite ausgibt
und dass die anderen Eigenschaften Werte zurückgeben, sodass wir sie im Template
nutzen können.

Jetzt können wir die `Pagination`-Klasse in unser Views-Modul importieren und
etwas Code zu der `list`-Funktion hinzufügen:

.. sourcecode:: python

    from shorty.utils import Pagination

    @expose('/list/', defaults={'page': 1})
    @expose('/list/<int:page>')
    def list(request, page):
        query = URL.query.filter_by(public=True)
        pagination = Pagination(query, 30, page, 'list')
        if pagination.page > 1 and not pagination.entries:
            raise NotFound()
        return render_template('list.html', pagination=pagination)

Die If-Bedingung in dieser Funktion versichert, dass ein Statuscode 404
zurückgegeben wird, wenn wir nicht auf der ersten Seite sind und es keine
Einträge zum Anzeigen gibt (einen Aufruf von ``/list/42`` ohne Einträge auf
dieser Seite nicht mit einem 404 zu quittieren, wäre schlechter Stil).

Und schließlich das Template:

.. sourcecode:: html+jinja

    {% extends 'layout.html' %}
    {% block body %}
      <h2>URL Liste</h2>
      <ul>
      {%- for url in pagination.entries %}
        <li><a href="{{ url.short_url|e }}">{{ url.uid|e }}</a> &raquo;
            <small>{{ url.target|urlize(38, true) }}</small></li>
      {%- else %}
        <li><em>keine URLs bis jetzt gekürzt</em></li>
      {%- endfor %}
      </ul>
      <div class="pagination">
        {%- if pagination.has_previous %}<a href="{{ pagination.previous
            }}">&laquo; Vorherige</a>
        {%- else %}<span class="inactive">&laquo; Vorherige</span>{% endif %}
        | {{ pagination.page }} |
        {% if pagination.has_next %}<a href="{{ pagination.next
          }}">Nächste &raquo;</a>
        {%- else %}<span class="inactive">Nächste &raquo;</span>{% endif %}
      </div>
    {% endblock %}


Bonus: 404-Fehlerseiten gestalten
=================================

Jetzt, da wir unsere Anwendung fertig gestellt haben, können wir kleine
Verbesserungen vornehmen, zum Beispiel eigene 404-Fehlerseiten.  Das ist
ziemlich einfach.  Das Erste, was wir machen müssen, ist eine neue Funktion
namens `not_found` in den Views zu erstellen, welche ein Template ausgibt:

.. sourcecode:: python

    def not_found(request):
        return render_template('not_found.html')

Dann müssen wir in unser Anwendungsmodul wechseln und die `NotFound`-Exception
importieren:

.. sourcecode:: python

    from werkzeug.exceptions import NotFound

Schließlich müssen wir sie auffangen und in eine Response umwandeln.  Dieser
except-Block kommt **vor** den except-Bock mit `HTTPException`:

.. sourcecode:: python

    try:
        # das bleibt das gleiche
    except NotFound, e:
        response = views.not_found(request)
        response.status_code = 404
    except HTTPException, e:
        # das bleibt das gleiche

Jetzt noch ``templates/not_found.html`` hinzufügen und du bist fertig:

.. sourcecode:: html+jinja
    
    {% extends 'layout.html' %}
    {% block body %}
      <h2>Seite nicht gefunden</h2>
      <p>
        Die aufgerufene Seite existiert nicht auf dem Server.  Vielleicht
        willst du eine <a href="{{ url_for('new') }}">neue URL
        hinzufügen</a>?
      </p>
    {% endblock %}


Abschluss
=========

Dieses Tutorial behandelt alles, was du brauchst, um mit Werkzeug, SQLAlchemy
und Jinja anzufangen und sollte dir helfen, die beste Lösung für deine Anwendung
zu finden.  Für einige größere Beispiele, die außerdem einen anderen Aufbau und
Ideen zum Ausführen benutzen, solltest du mal einen Blick auf den
`Beispielordner`_ werfen.

In diesem Sinne: Viel Spaß mit Werkzeug!

.. _Beispielordner: http://dev.pocoo.org/projects/werkzeug/browser/examples
