django - 15.7.2005 - 9.2.2006

Django-Vorlagen sind nicht begrenzt

shannon -jj behrens hält die Django-Vorlagensprache für begrenzt - weil sie keine Funktionen mit Parametern hat, um HTML-Snippets wiederzuverwenden. Natürlich ist die offizielle - und vereinfachte - Antwort darauf, dass die Django-Vorlagensprache absichtlich so einfach ist, damit sie leicht von Nicht-Programmierern gelernt werden kann (da Designer nicht unbedingt Programmierer sind). Das ist eine ziemlich gute Begründung, aber ich denke, das ist ein bisschen zu vereinfacht.

Hier ist die längere - vollständigere - Antwort auf diesen Vorwurf: Die Django-Vorlagensprache ist überhaupt nicht begrenzt. Ja, ich weiß, dass die "include" und "block" Tags nicht parametrisierbar sind und daher nicht oft für komplexere Situationen nützlich sind (zumindest, wenn Sie nicht in der Namensraum-Hölle enden wollen, weil Sie einige Template-Globals im Kontext weitergeben).

Was sollten Sie also tun, wenn Sie feststellen, dass Ihre Vorlagen komplexeren Code benötigen? Eine Möglichkeit wäre, die Daten in der View-Funktion vorzuberechnen und sie über den Kontext an die Vorlage weiterzugeben - auf diese Weise hat die Vorlage die fertigen Daten und kann sie direkt präsentieren.

Aber was tun, wenn Sie nicht vorberechnen können, weil Sie generische Ansichten verwenden? Sie könnten Ihre generische Ansicht mit Ihrem eigenen Code umhüllen und die ursprüngliche generische Ansicht in dieser Funktion mit dem modifizierten Kontext aufrufen. Auf diese Weise haben Sie denselben Vorteil wie oben - Ihre Vorlagen haben die Daten sofort zur Verfügung. Wenn Sie viele View-Funktionen haben, die alle die gleiche Kontextanreicherung benötigen, können Sie Ihren Wrapper als Dekorator schreiben - und einfach die generischen Ansichten dekorieren und diese dekorierten Funktionen in Ihren urlpatterns verwenden.

Aber was, wenn auch das Umhüllen keine Lösung ist? Gibt es nicht eine Möglichkeit, komplexeren Code zu schreiben, ohne all dieses Umhüllen? Natürlich gibt es das! Die Antwort sind benutzerdefinierte Vorlagentags. Das mag zwar ein bisschen übertrieben klingen, aber glauben Sie mir, das Schreiben einiger Vorlagentags ist wirklich nicht so schwer. Es gibt Dokumentation zur Verwendung und Erweiterung des Vorlagensystems in Python

Ein noch einfacherer Weg, Ihre eigenen Tags zu schreiben, ist die Verwendung der "simple_tag" oder "inclusion_tag" Hilfsprogramme in django.template.Library. Diese Funktionen ermöglichen es, einfache Tags sehr einfach zu erstellen - das inclusion tag basiert auf einem Vorlagensnippet, sodass Sie es als eine Vorlagenfunktion mit Parametern betrachten können. Eine Menge von benutzerdefinierten Vorlagen wird in den contrib/admin Sachen verwendet.

Das Hauptproblem mit den neueren Dingen im Code ist, dass die Dokumentation fehlt. Hoffentlich wird das mit der Zeit gelöst. Aber bitte, wenn das nächste Mal jemand versucht, Ihnen zu sagen, dass die Django-Vorlagensprache zu primitiv ist, glauben Sie ihm nicht. Die Django-Vorlagensprache ist leicht für Nicht-Programmierer zu verstehen - aber sie ist sehr erweiterbar für Python-Programmierer. Und Sie erweitern sie in der Sprache, die Sie mögen - in Python.

Django Paste - Ian ist starting to integrate Django with paste (and paste deploy). I for one will most definitely try to support that, so his list of related tickets is already down by one. Paste deploy might even be taken as the future default FCGI/SCGI solution - because it uses the same FLUP lib, it is as capable as my scripts, but due to the structure of Paste, installation should be much easier (and might even be standard in the future with Python hosters).

jacobian.org : Django performance tips - Jacob, einer der Dango Core-Devs schreibt über Performance-Tuning für Django Applikationen. Deckt sich stark mit meinen Erfahrungen.

Benutzerkennwörter in der Administration festlegen

Ein eher hässlicher, aber dennoch nützlicher Monkeypatch:

# monkey-patch für auth.users
from django.models.auth import User

def user_pre_save(self):
    if not self.password.startswith('sha1$'):
        self.set_password(self.password)

User._pre_save = user_pre_save

Fügen Sie dies in Ihre Modelldatei (oder an einen anderen Ort, der früh geladen wird) ein, und Sie können Passwörter im Admin-Bereich durch Eingabe von Klartext-Passwörtern festlegen. Wenn das Passwort mit 'sha1$' beginnt, wird es als bereits verschlüsselt angesehen und es passiert nichts. Wenn es nicht mit dieser Zeichenfolge beginnt, wird es mit der Standard-Django-Funktion zur Passwortverschlüsselung umgewandelt.

Nein, dies ist nichts, das in den Kern gehört - es ist viel zu hässlich dafür. Aber zumindest ermöglicht es Ihnen, Passwörter über den Admin-Bereich festzulegen, ohne dass der Benutzer den tatsächlichen Passwort-Hash berechnen muss.

Weblog-Umzug

Tja, also jetzt ist es soweit - ich werde mein Weblog hier auf die neue Software umziehen - endlich kein PHP mehr für mein Haupt-Blog. Im Moment laufen noch beide Systeme getrennt, ich synchronisiere nur die Inhalte auf das neue Blog, in den nächsten Tagen werde ich aber hier einen Redirector installieren der alle möglichen URLs die wichtig sind auf das neue System umleitet. Kommentare gehen weitestgehend rüber, nur die Kommentare zu Blogmarks gehen flöten, die neue Software hat für Links keine eigene Seite mehr auf der Kommentare liegen könnten - macht eh keinen Sinn, wer über die Links diskutieren will, sollte die Kontaktmöglichkeiten der verlinkten Seite benutzen.

Ansonsten ist das neue System natürlich komplett mit Django erstellt - endlich alles in Python. Das war auch der Hauptgrund. Ausserdem wurde mir der immer weiter steigende Pagerank und die ganzen vielen Links und der - für meine Erwartungen riesige - Traffic langsam unheimlich, da muss man was gegen machen. Und das einfachste ist da immer noch die Domain zu wechseln

Achso, Feeds werden natürlich auch umgeleitet, aber wer will kann schon den neuen Feed unter der neuen Adresse eintragen.

Wem was an dem neuen System auffällt, entweder hier oder drüben in die Kommentare schreiben (halt da wo es dann funktioniert) schreiben. Ich hab zwar so ziemlich alles getestet, aber es schleichen sich trotzdem immer mal wieder Fehler ein ...

Mal wieder was von der Bastelfront

Content-type: matter-transport/sentient-life-form - für die, die mal schnuppern wollen, wo es mit meinem Blog hingehen wird. Noch nicht ganz fertig, einige Bugs in meiner Software, ein paar Sachen warten auf Patches in Django, aber im grossen und ganzen bin ich schon ganz zufrieden.

JobControl - Django Projects - Trac - ein einfaches Jobsteuerungssystem für Django, mit dem man Hintergrundjobs einstellen kann.

Manches ärgert mich fürchterlich

Zum Beispiel, wenn Umlaute nicht sauber verarbeitet werden - wie bei dem pre_populate_from bei Django. Daher benutze ich das einfach nicht mehr in meinem CMSProject, sondern fülle den Slug einfach im _pre_save. Und lasse dann dort eine entsprechende Routine laufen. Wobei auch diese nicht wirklich perfekt ist, aber immerhin brauchbar ...

Und ja, das hier ist ein Testbeitrag für die Funktion um aus einem Titel mit Umlauten einen Slug zu machen.

Ein Test-Framework für Django

DjangoTesting ist Teil meines DjangoStuff-Projekts und der Beginn eines Testframeworks für Django, das dem Testframework von Ruby on Rails nachempfunden ist. Derzeit sind nur Modelltests implementiert, Request/Response-Tests sind geplant.

Das Testframework basiert ausschließlich auf unittest und django, sodass Sie keine zusätzlichen Module benötigen (außer meinem DjangoStuff-Projekt natürlich). Es bietet python-basierte Fixture-Notationen (Fixtures sind einfach nur Python-Klassen mit Attributen in einer DATA-Unterklasse) und ein grundlegendes Befehlszeilen-Tool, um diese Tests und Fixtures zu nutzen.

Tests und Fixtures werden in Anwendungen und Projekten gespeichert, sodass Sie anwendungsspezifische Tests haben können (besonders nützlich bei generischen Anwendungen) und projektübergreifende Tests, die mehrere Anwendungen integrieren.

Ich denke, ein gutes Testframework wäre wirklich wichtig für Django-Anwendungen, insbesondere für Anwendungen, die zwischen Projekten geteilt werden sollen. Aber ich denke auch, dass ein gutes Testframework auch etwas "Banging" braucht - also habe ich es als kleines Subprojekt für mich selbst gestartet. Aber wenn es zu etwas Nützlichem heranwächst, werde ich mich für die Aufnahme in den Django-Trunk entscheiden.

Case/When/Otherwise für Django

Wenn du böse Pläne für eine Switch-Anweisung in Django hast (hia rjwittams!), könntest du einen Blick auf mein TagLib werfen. Dort gibt es eine case/when/otherwise-Anweisung. Sie ist ganz einfach zu verwenden:

{% case variable %}
{% when "value1" %}
{% endwhen %}
{% when "value2" %}
{% endwhen %}
{% otherwise %}
{% endotherwise %}
{% endcase %}

Der Grund für die Tag-Struktur ist, dass der Django-Template-Parser in der parsefor-Funktion nur nach parameterlosen Block-Schließtags sucht und du daher keine einfache Lösung wie diese verwenden kannst:

{% if condition %}
{% elif condition %}
{% else %}
{% endif %}

Du müsstest viel von dem Template-Parser in eine parsefor-Funktion kopieren, die nach einem Token mit einem Tag und Parametern sucht, um den aktuellen Block zu schließen.

Ich habe mich daher für den Ansatz mit den bereichsbezogenen Tags entschieden, bei dem das "case"-Tag nur eine Kontextvariable "case" setzt und sie mit einem Wörterbuch mit "value" und "fired" füllt - wobei letztere ein Auslöser ist, der von jedem "when"-Tag ausgelöst werden kann, um zu verhindern, dass andere "when"-Tags oder das "otherwise"-Tag sich selbst auslösen. Ein bisschen hässlich, aber funktionierend.

Ad-hoc-Organisation in CM-Systemen

Adhoc organization ist der Name, den ich den grundlegenden Designentscheidungen für mein neues Content-Management-System (Blog-System, persönliches Wiki, digitale Bilderschuhschachtel - was auch immer) gegeben habe. Es kommt gut voran, auch wenn ich es bisher nur als Beispielanwendung genutzt habe, um meine kleinen Tools aus dem DjangoStuff-Pseudo-Projekt zu nutzen. Und es ist immer noch eine der besten Möglichkeiten zu sehen, wie Tagging oder Suche oder der neue Kalendertag oder andere Dinge genutzt werden.

Aber es kommt so gut voran, dass ich denke, ich werde in der Lage sein, einige Websites in naher Zukunft umzustellen. Die grundlegenden Designentscheidungen sind in dem verlinkten Dokument in meinem Trac-Wiki etwas dokumentiert. Das Hauptziel für mich ist es, etwas zu bekommen, das ich genauso einfach für die Bildpräsentation wie für die Textpräsentation nutzen kann und das mir ermöglicht, beide Teile wirklich zu integrieren. So dass Artikel wirklich aus einer Vielzahl von Medien und Text bestehen können.

Es macht Spaß, an einem Projekt zu arbeiten, bei dem man das Modell von Zeit zu Zeit abreißen und einen Teil davon neu aufbauen kann oder große Refactoring-Entscheidungen trifft, die einen für eine Weile mit einem kaputten Haufen Python-Bullshit zurücklassen.

"Fitting on" some framework

Wie können Sie feststellen, ob ein Framework zu Ihrer Denkweise passt? Es ist nicht so, dass Sie einfach in den Spiegel schauen könnten, um zu sehen, ob es Ihnen gefällt. Sie benötigen andere Wege, um das zu entscheiden. Eine Möglichkeit, dies zu entscheiden, ist die Produktivität - wie schnell Sie Ihr Projekt zum Laufen bringen.

Aber erzählt Ihnen das wirklich die ganze Geschichte? Was, wenn das Projekt etwas völlig anderes gewesen wäre? Haben Sie einfach den Sweet Spot des Frameworks getroffen? Hatten Sie einfach nur Glück?

Eine Möglichkeit, zu entscheiden, ob ein bestimmtes Framework, eine Sprache oder ein Werkzeug zu meiner Arbeitsweise passt, besteht darin, die grundlegenden Abstraktionen zu betrachten, die mir dieses Werkzeug bietet. Und zu prüfen, wie ich sie verwenden kann und wie natürlich sie zu meinem Denken passen - stoße ich auf Probleme, weiß ich nicht sofort, welche Abstraktion ich verwenden soll, welches Werkzeug ich ziehen soll? Oder fallen die Dinge einfach an ihren Platz?

Ich habe relativ früh entdeckt, dass ich in der Programmierung etwas ungewöhnlich bin, weil ich nicht meine eigenen Abstraktionen baue und versuche, sie in das zu übersetzen, was die Sprache oder das Framework mir gibt, sondern dass ich beginne, direkt in den Abstraktionen und Syntaxen zu denken, die mir gegeben werden - aber nur wenn sie zu meiner Art passen.

Das ist für mich also das ultimative Maß dafür, ob ein Framework wirklich in mein Denken passt: von Zeit zu Zeit zu prüfen, ob ich Übersetzungen versuche oder ob die Dinge einfach fließen. "In den Flow zu kommen" ist für mich heutzutage das Wichtigste.

Wie schneidet Django also ab? quite nicely. Es gibt mir wirklich, was ich in den meisten Fällen brauche, es gibt nur sehr wenige Bereiche, in denen "der Flow" unterbrochen wird, in denen ich um Probleme herumdenken muss, anfangen muss, Übersetzungen zu machen. Ein Bereich ist das spezielle Verhalten von Eingabefeldern - dies wird derzeit in Django mit parametrisierten Instanzen von vordefinierten Feldklassen durchgeführt. Es gibt keine wirklich schöne Möglichkeit, Subklassen zu erstellen, man endet damit, Code aus anderen Teilen der Django-Quelldatei zu kopieren - definitiv "den Flow" brechend.

Aber die meisten anderen Teile fallen einfach an ihren Platz: Middleware für die globale Verwaltung des Request-Response-Bereichs. Template-Loader für - nun ja - Template-Loading (ja, es ist keine große Sache - aber in der Lage zu sein, Ihren eigenen Template-Loader zu schreiben, ist wirklich hilfreich). Die urlpatterns - hey, das ist wirklich eine coole Idee, weil Sie aufgrund der absolut lockeren Kopplung nicht einmal versuchen, Ihre URLs nach Ihrer Code-Struktur zu modellieren, sondern dazu neigen, sie zu entwerfen. Und so sollte es sein.

Models sind leistungsfähig genug, um die modellbezogene Funktionalität wirklich dorthin zu verlagern (obwohl das Class MODULE-Zeug es noch schöner machen wird, besonders das etwas hässliche module_globals-Ding). Es wäre cool, wenn Model-Klassen Mixin-Klassen unterstützen würden, so dass abstrakte Apps Dinge bereitstellen könnten, die einfach von den Benutzern referenziert werden, um Funktionalität hinzuzufügen. Aber Sie können viele dieser Probleme mit generierten Klassen lösen - dank Python-Introspektion (obwohl Sie ein wenig über die Django-Modellmagie wissen müssen).

Die meisten komplexen Dinge tendieren dazu, in Template-Tags und generische Ansichten zu gehen - mein CMS-Projekt hat derzeit nur 3 Ansichtsfunktionen seiner eigenen, der Rest ist in generische Ansichten abstrahiert (für die Suche und das Tagging). Template-Tags könnten etwas einfacher zu schreiben sein, insbesondere der Parser ist zu primitiv - eine Bibliothek von Hilfsfunktionen zum einfachen Zerlegen der Tag-Zeichenkette wäre gut (hey, vielleicht schreibe ich eine, die Grundlagen sind bereits in meinem SVN-Repository).

Template-Filter sind ein bisschen ein hässliches Entlein - sie sehen den Anforderungszusammenhang nicht, so dass sie nicht viel mehr tun können, als das eingehende Objekt und einige konstante Parameter zu übernehmen. Ich denke, sie sollten den Kontext übergeben bekommen, so dass sie, wenn nötig, ein bisschen schlauer sein könnten (wie z.B. das Auflösen eines Parameters gegen den Kontext durch Filter ermöglichen).

Generische Ansichten sind auch ganz schön - auch wenn ich die vordefinierten nicht so oft verwende. Der Hauptgrund dafür ist, dass ich oft damit ende, die generischen Ansichten in einigen Code zu wickeln, der ihr Verhalten modifiziert - und dann ist es oft einfacher, einfach meine eigenen zu erstellen. Aber sie sind großartig für den ersten Einstieg in Bereiche, hängen Sie sie einfach in Ihr Projekt ein und die Funktionalität ist verfügbar. Sie können sie immer durch Ihre eigenen Ansichtsfunktionen austauschen, wenn Sie feststellen, dass Sie sie benötigen.

Und der Admin, das eine Ding, das Django aus der Masse herausstechen lässt? In meinen ersten Spielprojekten habe ich ihn geliebt, in späteren habe ich ihn nicht verwendet (die Galerie braucht ihn nicht), aber mit dem CMS-Projekt habe ich das erste Projekt gemacht, das ihn wirklich stark nutzt. Und ich muss sagen, ich mag ihn. Er sollte etwas flexibler werden (der new_admin-Zweig könnte dabei helfen, da er mehr Dinge in Templates verschiebt, so dass sie überschrieben werden können), aber insgesamt ist er wirklich cool und nützlich.

Zwei Dinge sind jedoch definitiv für den Admin erforderlich: vollständige Transaktionsunterstützung, die an die Anforderung-Antwort gebunden ist (Ticket #9 im Django Trac), weil das Ändern von Dingen und das Ende mit inkonsistenten Tabellen kein Spaß ist. Wie z.B. eine Ausnahme zu erhalten, weil etwas in repr gebrochen ist, so dass der Log-Eintrag nicht geschrieben wird, aber das Objekt wird geschrieben. Natürlich bemerken Sie es nicht, gehen zurück, senden erneut und enden mit zwei Objekten und immer noch keiner Log-Nachricht ...

Das andere, was benötigt wird: grundlegende Haken für objektbasierte Authentifizierung. Kein vollständiges ACL oder so etwas, nur einige wirklich einfache Haken vom Admin zum Modell, die der Benutzer definieren kann, um dem Admin mitzuteilen, ob ein bestimmtes Objekt bearbeitbar sein sollte oder nur schreibgeschützt angezeigt werden sollte. Das Hauptproblem mit der aktuellen Lösung ist, dass sie nur vollständige Tabellen behandelt - Sie können dem Admin nicht einmal mitteilen, dass ein bestimmter Benutzer nur an der aktuellen Site arbeiten kann und keine Objekte anderer Sites ändern kann (mein CMS-Projekt macht starken Gebrauch von der Multi-Site-Fähigkeit in Django - ein Admin-Server sollte mehrere Sites in einer Admin-Oberfläche verwalten).

Aber alles in allem ist das Erstellen von Web-Apps mit Django wirklich Spaß. Es ist nicht nur produktiv für mich, es fühlt sich natürlich an, Dinge auf die Django-Art zu tun. Also ja, Django passt zu meinem Denkstil. Scheint genau ins Schwarze getroffen zu haben.

Markdown für Django

Django enthält bereits einen Markdown-Filter (in contrib.markup), dennoch habe ich meine eigene Markdown für Django Mini-Anwendung erstellt. Die Hauptvorteile sind die Verknüpfung mit Django-Modellen (durch Verwendung generischer Modellabfragen und get absolute url), eine generische Dokumentationsansicht, die das Umschalten der Sprache handelt, und eine schöne Makro-Einrichtung für Markdown. Makros sind eine nützliche Möglichkeit, Markdown zu erweitern, indem Django-Vorlagenschnipsel geschrieben werden, die immer dann aufgerufen werden, wenn der Benutzer das Makro in seiner Markdown-Quelle aufruft.

Es war früher Teil des CMS-Projekts, aber ich denke, es ist nützlich in sich und so viel besser in das Stuff-Pseudo-Projekt einzubauen.

Generischer Suchdienst für Django

Wenn Ihre Django-Anwendung Suchfunktionen benötigt, können Sie Ihre eigene Lösung entwickeln. Oder Sie können meine generische Suchansicht verwenden. Diese bietet einen Parser für Abfragen und eine Suchmaschinerie, die für moderate Datenbankgrößen geeignet ist. Sie bietet eine erweiterbare, google-ähnliche Syntax.

Das Hauptproblem ist, dass Django keine OR-Abfragekombinationen unterstützt und dass es keine "icontainsnot"-Abfragen unterstützt. Die Suchmaschine führt daher mehrere Selektionen durch, um eine Abfrage zu erhalten. Sie beginnt mit dem längsten Suchbegriff und geht von diesem Ergebnisdatensatz aus in der Größe nach unten, wobei sie ihn von einem Schritt zum nächsten einschränkt. Da sie jedoch den letzten Ergebnisdatensatz im Speicher behalten muss (mindestens die Liste der IDs), kann dies Probleme für Ihren Server verursachen, wenn Ihre Datenbank zu viele Zeilen enthält (insbesondere wenn die Benutzer dumme Abfragen durchführen, die große Ergebnisdatensätze erzeugen).

Vielleicht wird dies in Zukunft einige Optimierungen lernen, um es besser mit größeren Datenbanken funktionieren zu lassen, aber es ist ganz gut als Suchmaschine für Ihren Blog oder Standard-Content-Management-Systeme.

Sehr einfache Ansichtsfunktionen

Manchmal hast du eine ganze Reihe von wirklich einfachen View-Funktionen in deinem Django-Projekt (ja, das ist für dich, bitprophet!). View-Funktionen, die eigentlich nicht mehr sind als ein einfacher Render-to-Response-Aufruf - nimm eine Vorlage, nimm einige Daten aus der Anforderung, packe sie hinein und rendere eine Antwort. Es ist eher langweilig, sie aufzuschreiben, und es verletzt das DRY-Prinzip. Was also tun? Schreibe deine eigene generische Ansicht.

from django.core.extensions \
 import render_to_response

def simple_view(request, template, **kwargs):
 return render_to_response(
 template, kwargs)

Das ist alles. Eine einfache und schöne View-Funktion, die genau das tut - eine Vorlage rendern. Sie kann sogar mit Kontextvariablen aus dem URL-Muster gefüttert werden. Verwende es wie folgt in deiner URL-Konfiguration:

urlpatterns = patterns('',
(r'^page/(?P<arg>.*)/$', 'cool.simple_view',
 {'template': 'app/mytemplate'}),
)

Auf diese Weise wird eine /page/foo/ Ansicht an die Vorlage 'app/mytemplate' mit einem Kontext geroutet, der nur die Variable 'arg' mit dem Wert 'foo' enthält. Und du wirst nie wieder diese einfachen View-Funktionen schreiben müssen. Für extra Würze könntest du einen context_instance = DjangoContext(request) in den Render-to-Response-Aufruf werfen, um sogar den authentifizierten Benutzer und so weiter aus der Anforderung zu erhalten.

Modul-Hacking für Django

Django selbst konstruiert dynamisch Modellmodule aus Ihren Modellklassen. Das habe ich in meinem ersten Ansatz für die abstrakte Tagging-Anwendung verwendet. Jetzt habe ich eine bessere Lösung in der aktuellen Version gefunden - ich kann das dynamische Modul selbst relativ einfach modifizieren, eine dynamische Modellklasse generieren und diese in das Modellmodul einfügen. Was es tatsächlich tut, ist nur das Nachahmen dessen, was passiert, wenn Python eine Klasse definiert - die meisten Dinge werden ohnehin von der Meta.ModelBase-Metaklasse in Django erledigt. Ich musste nur einige Modul-Hacking-Sachen hinzufügen. Python-Introspektion ist großartig!

Was Sie dadurch erhalten, ist eine viel sauberere Schnittstelle, um die Tagrelation-Klasse für Ihr Modell zu erstellen - nur ein Funktionsaufruf, kein albernes Subclassing oder überflüssige Zuweisung. Alles geschieht wie von Zauberhand.

Es ist Magie.

Tagging mit Django

Da die Frage, wie man Tagging mit Django umsetzt, ziemlich oft auftaucht, habe ich eine kleine Lösung für dieses Problem geschrieben: AbstractTagging. Dies ist eine generische Anwendung und generische Ansichten, die Ihnen eine sehr einfache Lösung bieten, um Tagging zu jedem Modell hinzuzufügen, das Sie in Ihren Django-Apps haben. Es wird derzeit von mir in meinem CMS-Projekt verwendet. Der Quellcode befindet sich im Stuff-Projekt.

Es war ein bisschen seltsam, es zu bauen, weil ich eine Basisklasse dynamisch konstruieren musste, die Sie in Ihren Modellen unterklassen können - dies liegt an der Magie in django.core.meta, wo Modellklassen in Module umgewandelt werden. Aber das Ergebnis ist, glaube ich, ganz schön.

Django als CMS verwenden

Ich arbeite derzeit an einer meiner Websites - Content-type: matter-transport/sentient-life-form. Es war zuvor eine Apache::MiniWiki-basierte Website und befindet sich nun im Übergang zu einer Django-basierten Website. Die Idee hinter dem Code für diese Website ist es, ein CMS auf Basis von Django zu erstellen, das vollständig die Django-Administration verwendet. Die Benutzer sollten also in der Lage sein, alle Verwaltung nur in der Administration durchzuführen, während die Website selbst ein wenig wie ein Wiki funktioniert. Autoverlinkung, Autoeditierung fehlender Seiten, Editlinks, Versionierung (derzeit in der Quelle fehlend) - all das sollte auf Basis der von der Django-Administration bereitgestellten Tools erfolgen.

Allerdings handelt es sich dabei nicht um eine vollständige Website - die verlinkte Website ist fast leer, ich habe nie viel dort hochgeladen. Es ist eher ein Projekt, um tiefer in die Django-Administration einzutauchen, um zu sehen, wie es ist, darin zu arbeiten - damit ich über diese Dinge Bescheid weiß, wenn ich beginne, echte Projekte zu erstellen.

Der Code selbst ist frei verfügbar - und es gibt bereits eine nette Sache darin. Es ist ein Template-Loader, der Inhalte aus der Datenbank statt aus dem Dateisystem oder aus Python-Eiern lädt. Es ist unter #633 bei Django "ticketed", sodass es eines Tages in django.contrib aufgenommen werden könnte.

Django i18n status

I worked a bit more on the i18n stuff in django today and finally switched my gallery to the i18n branch. You can now see the strings on that site with either english or german settings. Other languages get english output (so if you are in Germany and still see english strings - check the language settings in your browser, wether German is defined with higher priority than English).

The code works quite nice and I think I will give it a week or so to settle and than start to put finishing touches to it - like adding much more translation hooks to the django source.

IRC Logger-Aktualisierung

Der IRC-Logger funktioniert einwandfrei, aber ich war nicht zufrieden mit der Abhängigkeit von muh - also habe ich meinen eigenen kleinen Logger-Bot in Python geschrieben, basierend auf irclib. Er funktioniert gut und tut genau das, was ich möchte - Logging. Ich fühle mich immer ein bisschen unwohl, wenn IRC-Bots Befehlsstrukturen und so etwas haben und ich brauche eigentlich nichts davon ...

Jetzt ist das Projekt größtenteils abgeschlossen - Sie müssen nur das Django-Admin verwenden, um Kanäle zu Ihrer Datenbank hinzuzufügen, den Logger-Bot auf einen IRC-Host verweisen und sehen, wie er Kanäle beitritt und mit dem Logging beginnt.

Oh, es gibt noch Dinge zu tun - zum Beispiel muss der Bot die Liste der Kanäle neu scannen, damit er neu hinzugefügte Kanäle erkennt und gelöschte Kanäle verlässt (und vielleicht sollte ich die Aktivierung/Deaktivierung von Kanälen hinzufügen, damit ich Kanäle für eine Weile deaktivieren kann, ohne die Archive zu verlieren), aber für den Moment loggt er nur #django und dafür ist es gut genug.

IRC-Logger für #django

Es gibt jetzt einen IRC-Logger für #django auf freenode.net. Seit der loglibrary ausgefallen ist, dachte ich darüber nach, meinen eigenen zu erstellen. Also begann ich, die für einen IRC-Logger benötigten Dinge zu bauen. Die Schnittstelle selbst ist natürlich mit django geschrieben.

Wie immer ist der Quellcode in meiner Trac-Instanz verfügbar. Derzeit läuft er nur in #django, aber er kann leicht auf andere Kanäle erweitert werden. Und er speichert die Protokollzeilen in einer Datenbank, sodass ich in der Lage sein werde, Suchfunktionen und dergleichen hinzuzufügen. Er hat bereits eine Kalenderansicht der Protokolle.

Das Protokollieren selbst erfolgt mit muh - ein netter IRC-Proxy, der das Protokollieren in Named Pipes ermöglicht. Dann gibt es ein Skript fetch.py, das die Zeilen aus der Named Pipe zieht und sie in der Datenbank speichert. Der letzte Teil ist der django-basierte Viewer für diese Protokolle.

Aktualisierung: Das Protokollieren erfolgt jetzt mit einem dedizierten IRC-Logger, der in Python geschrieben ist. Er befindet sich im Quellcodebaum als loggerbot.py.

Ich hätte generische Ansichten in Django verwenden können, nur dass ich sie parameterisieren musste. Das habe ich mit einer Wrapper-Funktion gemacht, die Dinge von den Schlüsselwörtern einer View-Funktion zu den extra_lookup-Argumenten und den extra-Kontext-Schlüsselwörtern der generischen Ansichten verschiebt. Sie können den Code im Repository sehen.

Der Rest ist nur Standard-Django-Zeug: generische Vorlagen (die den coolen regroup-Tag nutzen) und einige benutzerdefinierte Tags für den Kalender und die Benutzerfarbgebung. Ein bisschen Modell-Hackerei und das war's eigentlich. Schön und einfach. Es hat nur ein paar Stunden gedauert, um das zusammenzubauen, und das schließt das Herumspielen mit muh und Named Pipes ein ...

i18n und django

Jacob hat mir die Berechtigungen für Branch-Commits und einen Branch für i18n-Sachen eingerichtet. Daher habe ich heute an den Ideen im Patch von Ticket 65 von nesh gearbeitet. Ich habe die meisten Sachen von Grund auf neu geschrieben, weil ich einige Dinge etwas anders haben wollte, und jetzt ist es zum Testen verfügbar.

Zuerst, wie Sie die i18n-Sachen mit Ihrem Django-Checkout verwenden können. Sie benötigen einen aktuellen svn trunk Checkout und gehen Sie zum Root Ihres Checkouts und führen Sie den folgenden Befehl aus:

svn switch http://code.djangoproject.com/svn/django/branches/i18n/

Danach sollten Sie einen Baum mit meinen Patches angewendet haben. Ich habe bisher nur sehr wenig übersetzt, um den Patch und die Änderungen so klein wie möglich zu halten, aber ich habe bereits eine deutsche Übersetzungsdatei mit Sachen für den Admin-Index und den isAlphaNumeric-Validator hinzugefügt. Ich denke, ich werde bald noch mehr Sachen zu den Übersetzungen hinzufügen.

Der Patch behandelt nur den Übersetzungsanteil - andere Dinge wie Datumsformatierung, Zahlenformatierung, Zeitzonenbehandlung sollten in verschiedene Patches gehen, um jeden von ihnen so klein wie möglich zu halten. Das Übersetzungsobjekt (das ist das Biest, das für die Umwandlung von Zeichenfolgen in ihre neue Form verantwortlich ist) wird auf Anfrage erstellt. Dies gibt uns die Möglichkeit, verschiedene Stellen zu betrachten, die bei der Entscheidung helfen können, welche Sprache dem Benutzer präsentiert werden soll. Der Code beginnt damit, in der Sitzung nach einer django_language-Variablen zu suchen. Wenn diese nicht gefunden wird, sucht er in den Cookies nach einem django_language-Cookie. Wenn auch das nicht gefunden wird, gräbt er in den HTTP-Headern. Es sucht nach dem Http-accept-language-Header und teilt diesen nach Sprachen auf und sortiert sie nach Vorliebe. Es wird die erste Sprache (sortiert nach Vorliebe, höchste Vorlieben zuerst) verwenden, die in dem django-Nachrichtenfile-Repository gefunden wird. Wenn keine dieser Sprachen gefunden werden kann, wird es letztlich auf das Standardübersetzungsobjekt zurückgreifen, das durch die LANGUAGE_CODE-Einstellung in Ihrer Einstellungsdatei definiert ist. Nachrichten-Dateien können an drei verschiedenen Stellen gespeichert werden: Die django-Projekt-Nachrichten-Dateien werden im django.conf-Paket in einem Unterverzeichnis namens locale gespeichert. Dies ist ähnlich wie die admin_media- und admin-Vorlagenverzeichnisse. Das locale-Unterverzeichnis ist wie typisch bei der Speicherung von lokalen Daten strukturiert: ein Unterverzeichnis pro Sprache und ein LC_MESSAGES-Verzeichnis darin. Der Sprachbereich für django-Nachrichten-Dateien ist immer django. Der nächste Ort, an dem django nach Nachrichten-Dateien sucht, ist das Projekt - wenn Sie ein locale-Verzeichnis in Ihrem Projekt haben, können Sie dort zusätzliche Nachrichten-Dateien speichern. Der dritte Ort ist die Anwendung - Sie können ein locale-Verzeichnis neben Ihrem Anwendungs-Views-Verzeichnis haben. Alle locale-Verzeichnisse sind gleich strukturiert.

Ein Übersetzungsobjekt für eine bestimmte Sprache ist tatsächlich eine Verkettung von vier Übersetzungsobjekten: zuerst das Anwendungsübersetzungsobjekt. Dies wird auf das Projektübersetzungsobjekt zurückgreifen. Dieses wiederum wird auf das globale Übersetzungsobjekt zurückgreifen, das auf das Übersetzungsobjekt für die Standardsprache zurückgreifen wird. Auf diese Weise können höhere Ebenen Übersetzungen von niedrigeren Ebenen überschreiben und Anwendungen können ihre eigenen Übersetzungen bereitstellen.

Die Anwendung für die Übersetzungen wird tatsächlich durch Modulintrospektion entdeckt - es verwendet die View-Funktion, um auf eine URL zuzugreifen, um herauszufinden, welche Anwendung diese View-Funktion trägt, und verwendet diese, um nach lokalen Übersetzungen zu suchen. Es gibt zwei Tools, die zur Verwaltung von Übersetzungen bereitgestellt werden: make-messages.py und compile-messages.py. Beide Tools können entweder im Root des django svn-Baums oder im Projektverzeichnis oder im Anwendungsverzeichnis aufgerufen werden. make-messages.py wird das aktuelle Verzeichnis und alles darunter nach zu übersetzenden Zeichenfolgen durchsuchen und wird eine django.po-Datei im locale-Verzeichnis für die gegebene Sprache erstellen. compile-messages.py wird einfach alle .po-Dateien in .mo-Dateien umwandeln. Das Hinzufügen von Übersetzungen ist einfach. In Python-Code umgeben Sie einfach Zeichenfolgen (nur Zeichenfolgenkonstanten!) mit ('...') oder ("..."). Dadurch werden diese Zeichenfolgen für die Übersetzung markiert, sodass make-messages.py sie herausziehen und in die .po-Dateien schreiben kann. Und es wird die Zeichenfolge zur Laufzeit übersetzen, unter Verwendung des aktuellen Übersetzungsobjekts, das aus der Anfrage ermittelt wurde. In Vorlagen gibt es das Vorlagentag {% i18n ('....') %} - dieselbe Syntax wie mit Python-Code, nur müssen Sie es als Vorlagentag umschließen. Diese Zeichenfolgen werden ebenfalls aus den .html-Dateien in die .po-Datei gezogen. Das i18n-Tag unterstützt die Zeichenfolgeninterpolation aus dem Kontext: {% i18n ('blah %(blubb)s blubber') %} würde zunächst die Zeichenfolge übersetzen und dann die Kontextvariable blubb in das übersetzte Ergebnis interpolieren.

Ein Tipp: Wenn Sie Zeichenfolgen zum Übersetzen schreiben, verwenden Sie keine positional parameters für die Interpolation (die %s-Sachen), sondern verwenden Sie stattdessen benannte Parameter (%(blah)s). Auf diese Weise können Personen, die Übersetzungen erstellen, die Zeichenfolge neu anordnen, ohne Ihren Code zu brechen - einige Sprachen haben eine andere Reihenfolge als Englisch.

Die Verwendung der Übersetzungen ist ebenfalls einfach: Sie müssen nur Ihre Standardsprache in der LANGUAGE_CODE-Einstellung festlegen und django.middleware.locale.LocaleMiddleware zu Ihrer Middleware-Einstellung hinzufügen. Sie müssen es an die Spitze setzen - besonders im Admin sollte es vor dem AdminUserRequired Middleware kommen - aber es muss nach dem SessionMiddleware sein, wenn Sie dieses verwenden.

Das ist erst der Anfang, spielen Sie damit und sagen Sie mir, wenn etwas schief geht. Der beste Ort, um es zu sagen, ist im Ticket im django trac.

Routes für Python

Sehr interessant: Ben Bangert hat Routes für Python portiert. Routes ist der Kern des Mappings von URLs auf Funktionen und zurück der in Ruby on Rails benutzt wird. Also ein allgemeiner Mechanismus mit dem aus einer URL ein Python-Objekt und aus einem Python-Objekt seine URL ermittelt werden kann - flexibel konfigurierbar.

Könnte auch als Element in Django interessant sein, als Alternative zum derzeitigen URL Pattern System. Das derzeitige System liefert ja recht elegant aus einer URL die aufzurufende Funktion - aber leider gibt es nicht diesen gleichen Weg zurück vom Objekt oder der Funktion zurück zur URL.

Ausserdem könnte Routes auch innerhalb von WSGI-Projekten interessant sein - es löst elegant einen kleinen Teilbereich und das so abstrakt, das es gut mit Sachen wie Python Paste(Ian Bickings Meta-Framework für WSGI-Anwendungen) harmonieren müsste.

DjangoScgi - Django-Projekte - Trac

Django mit Apache und SCGI und Django mit Apache und FCGI sind zwei überarbeitete Dokumentationen, wie man Django mit sowohl FCGI als auch SCGI unter Apache zum Laufen bringt. Ich verwende dieselben Teile wie bei meinen vorherigen Howtos, nur dass jetzt auch SCGI unterstützt wird.

Meine Galerie läuft derzeit mit der Apache+SCGI-Konfiguration, es ist ganz schön. Die Konfiguration in Apache ist viel schöner und sauberer als mit der FCGI-Konfiguration.

Seit 2007 funktionierten die Links auf dieser Seite nicht mehr, daher habe ich sie entfernt.

Django-Galerie-Status

Erneut Neuigkeiten von meinem Galerie-Projekt - es macht gute Fortschritte, auch wenn einige meiner neuesten Arbeiten nicht direkt sichtbar sind. Ich habe viel an dem Code gearbeitet und viele Änderungen an der Admin-Oberfläche vorgenommen. Dinge im Quellcode, die für andere Djangonistas interessant sein könnten:

  • Ich habe immer noch eine vollständig dateisystembasierte Galerie - aber ich habe einen im Datenbank gespeicherten Cache hinzugefügt. Wenn nun Inhalte vom Dateisystem geladen werden, prüft der Code direkt seinen Datenbank-Cache und aktualisiert diesen entsprechend. Dies führte zu einer umfangreichen Überarbeitung des Codes, so dass nun tatsächlich alles durch Modellobjekte gesteuert wird - die Cache-Einträge sind nur Teil des Django-Modells. Dadurch wird der Code viel einfacher und ermöglicht die nächsten beiden Änderungen.
  • Ich habe eine automatische Sitemap für Galerien hinzugefügt. Die ersten Versionen durchsuchten das Dateisystem, aber jetzt verwendet es einfach den Datenbank-Cache, um eine Ordnerhierarchie mit Miniaturansichten zu erstellen.
  • Ich habe RSS-Unterstützung hinzugefügt. Die Hauptgalerie-Auswahl verweist auf einen RSS-Feed über alle Galerien und die Ordner- und Bildansichten innerhalb einer Galerie verweisen auf einen RSS-Feed nur für Bilder innerhalb dieser Galerie. Dies macht von dem RSS-Framework von Django Gebrauch.
  • Die Ansicht, die Galerie-Ordner und Bilder anzeigte, wurde überarbeitet, um viel mehr von Django-ähnlichen Dingen Gebrauch zu machen: anstatt von Lazy Closures, die an die Vorlage übergeben werden, verwende ich nun benutzerdefinierte Vorlagen, die helfen, den Code der Ansicht drastisch zu reduzieren (ok, der Code wird in die Taglib verschoben, aber das ist eine viel bessere Entkopplung als zuvor).

Das Ergebnis: Die Galerie ist viel schneller, ich habe RSS laufen und habe automatische Sitemaps. Der Code selbst ist viel einfacher, da er modellgetriebener ist - der frühere Konflikt zwischen Bildordner-Modell-Dingen auf der einen Seite und FSObject-Instanzen auf der anderen Seite ist verschwunden - und die Cache-Wartung ist automatisch. Und die XMLRPC-Integration ist auch viel schneller. Alles in allem einige sehr nützliche interne Änderungen.

Andere Änderungen gibt es in der Verwaltungs-Oberfläche, wo Sie nun nicht nur die AJAX-Funktionen zum Ändern von Objektnamen und dem versteckten Zustand haben, sondern dies auch über einen Dateimanager tun können. Dieser Dateimanager wird auch um einfache Verwaltungsfunktionen wie Verschieben, Löschen usw. erweitert. Er kann bereits neue Ordner erstellen.

Nachrichten vom Galerieprojekt

Nachrichten von meinem Django Gallery Projekt: Es kommt gut voran. Wenn Sie Beispielcode für AJAX mit Django oder XML-RPC mit Django möchten - sehen Sie sich den Quellcode an. Ich habe Teile der Blogger API, metaWeblog API und MoveableType API implementiert - gerade genug, damit Photon Bilder in meine Galerie posten kann. AJAX wird verwendet, damit angemeldete Benutzer den Titel von Bildern und Ordnern einfach durch Klicken auf den Titel ändern können und um das Werkzeugkasten auf der Rückseite von Bildern zu aktivieren. Zusätzlich enthält die Galerie die üblichen PIL-Funktionen wie das Erstellen von Thumbnails - ich bin besonders stolz auf das Aussehen der umgedrehten Ecke für Ordner-Thumbnails. Andere interessante Dinge, die sich lohnen zu untersuchen, könnten die Verwendung von lazy evaluation sein, um Inhalte in die Vorlage zu schieben, ohne sie vorher zu berechnen - sie wird nur berechnet, wenn die Vorlage sie tatsächlich verwendet. Und interessant könnte die Abstraktion des dateisystembasierten Inhalts sein - nur der Basispfad wird im Datenbankmodell gespeichert, der Rest der Galerie befindet sich im Dateisystem (und kann so leicht mit FTP, SSH oder direkt auf der Unix-Shell verwaltet werden).

Zusätzlich können Sie es sich für die Handhabung der Authentifizierung ansehen - die Benutzerregistrierung ist noch nicht abgeschlossen, wird aber irgendwann folgen. Ebenso Kommentare und RSS - aber ich muss das zuerst schreiben. Und ich beginne, Dokumentation zu schreiben - Docstrings im Code und Dokumentationsseiten im Wiki.

DjangoGallery - Beispiel-App mit Beispielinstallation

Wieder einmal auf Englisch, da auch für #django interessant

Ich habe den Quellcode hochgeladen und das Repository sowie eine Trac-Instanz für meine Django-Projekte verfügbar gemacht. Das erste Projekt, das dort zu finden ist, ist die DjangoGallery - das ist das, was ich auf viele-bunte-bilder.de verwende. Ich habe eine erste Version einer Installationsanleitung geschrieben, die zeigt, wie man die Galerie auf der eigenen Website zum Laufen bringt - man könnte sie sogar in das eigene Projekt integrieren (allerdings werden einige kleinere Änderungen am Quellcode erforderlich sein - hauptsächlich das Ersetzen des Projektnamens "gallery" durch den Namen Ihres Projekts).

Die Anwendung ist noch nicht vollständig fertig. Es gibt viele Pläne, wie man sie erweitern kann, da sie meine Hauptmotivation sein wird, alle anderen Galerie-Software, die ich betreibe, zu entfernen (ich habe bereits PHP Gallery ersetzt und jetzt ziele ich auf meine alten mod_perl-Dinge ab und das letzte wird meine Wordpress-basierte Galerie sein), also seien Sie gewarnt, dass sie sich im Laufe der Zeit ändern wird.

Wenn Sie Fehler finden, können Sie gerne Tickets einreichen. Sie können auch Verbesserungswünsche einreichen - aber da das Hauptziel dieses Projekts darin besteht, meine eigenen anderen Galerieprojekte zu ersetzen, ist es unwahrscheinlich, dass ich viel Arbeit neben dem investieren werde, was nötig ist, um dieses Ziel zu erreichen. Zumindest vorerst - es gibt keine Grenzen dafür, was mit dem Code gemacht werden kann, nachdem ich alle PHP- und Perl-Codes ausgelagert habe.

Seit 2007 sind diese Links nicht mehr funktionsfähig, daher habe ich sie entfernt.

erste Django-Anwendung life

So, meine erste Django-Anwendung ist life - noch nicht fertig, aber im Moment schon mal so gut, das sie besser ist als das alte PHP-Gemölter das da vorher lief. Und zwar gehts hier um meine Bildergalerie unter viele-bunte-bilder.de. Im Moment gibts zwar die Benutzerregistrierung und alles was damit zusammenhängt - aber das werd ich noch irgendwann mal einbauen. Oder auch nicht. Schaun mer mal.

Auf jeden Fall ist es schon mal ganz nett - ich kann Bilder wieder direkt aus iPhoto hochladen (was der wichtigste Vorteil der alten Galeriesoftware war) und die Sachen liegen im Filesystem, nicht in einer Datenbank - was ebenfalls ein recht wichtiger Punkt war.

Die Software selber ist natürlich verfügbar - wer stöbern will, ich hab eine Trac-Instanz mit meinen Django-Spielereien aufgesetzt.

Seit 2007 ist nix mehr davon online.

Django hat einen wichtigen Schritt für den Release 1.0 gemacht: anonyme Sessions. Bisher waren ja Sessions bei Django an eine Benutzeranmeldung gekoppelt, aber jetzt gehts auch ohne Registrierung. Wesentlich netter als tausende Cookies beim Benutzer zu erzeugen.

CRUD mit Django

Create, Read, Update, Delete - die Standardfunktionen klassischer Interfaces - kann man mit Django sehr einfach zusammenbauen. Dazu gibt es die Generic Views. Auf Postneo gibts jetzt ein CRUD Tutorial, welches zeigt wie simpel solche Oberflächen mit Django zusammengestellt werden können.

A comparison of Django with Rails ist ein recht guter Vergleich von Rails und Django. Wobei Vergleich bei zwei Systemen die durchaus unterschiedliche Themen adressieren natürlich recht schwierig ist - aber der Artikel versucht zumindestens die beiden Frameworks gegeneinander zu positionieren.

Django, Apache und FCGI

In Django, lighttpd und FCGI, zweiter Versuch habe ich eine Methode beschrieben, wie man Django mit FCGI hinter einer lighttpd-Installation ausführen kann. Ich habe die Django-FCGIs als eigenständige Server ausgeführt, sodass Sie sie unter unterschiedlichen Benutzern als der Webserver ausführen können. Dieses Dokument gibt Ihnen die benötigten Informationen, um dasselbe mit Apache 1.3 zu tun.

Aktualisierung: Ich pflege meine Beschreibungen jetzt in meinem Trac-System. Siehe die Apache+FCGI-Beschreibung für Django.

Aktualisierung: Ich habe von der Verwendung von Unix-Sockets zur Verwendung von TCP-Sockets in der Beschreibung gewechselt. Der Grund ist, dass Unix-Sockets Schreibzugriff von beiden Prozessen - Webserver und FCGI-Server - benötigen und das manchmal schwer einzurichten ist. TCP-Sockets sind nur ein bisschen langsamer, aber viel einfacher einzurichten.

Zuerst die Hauptfrage, die einige stellen könnten: Warum Apache 1.3? Die Antwort ist einfach: Viele Menschen haben immer noch Apache 1.3 als ihren Hauptserver laufen und können nicht leicht auf Apache 2.0 aktualisieren - zum Beispiel, wenn sie große Codebasen in mod_perl oder mod_python ausführen, werden sie Probleme bei der Migration haben, weil Apache 2.0 mod_perl2 oder mod_python2 erfordert und beide nicht vollständig kompatibel mit älteren Versionen sind. Und obwohl lighttpd ein fantastischer Webserver ist, wenn Sie bereits Apache 1.3 ausführen, gibt es möglicherweise einfach keinen Bedarf für einen weiteren Webserver.

Was benötigen Sie also - neben den Python- und Django-Dingen - für Apache 1.3 mit FastCGI? Nur das mod_rewrite-Modul und das mod_fastcgi-Modul installiert, das ist alles. Beide sollten mit der Verteilung Ihres Systems geliefert werden. Sie werden immer noch alle Python-Dinge benötigen, die ich im lighttpd-Artikel aufgeführt habe.

mod_fastcgi ist etwas eigenwillig in seiner Installation, ich musste ein bisschen damit herumspielen. Es gibt ein paar Stolpersteine, an die ich denken kann:

  • Die Angabe des Sockets kann kein absoluter Pfad sein, sondern muss ein relativer Pfad in Bezug auf das FastCgiIpcDir sein.
  • Die Angabe des FCGI selbst (auch wenn es rein virtuell ist) muss in einer vollständig qualifizierten Form in Bezug auf das Dokumentenstammverzeichnis, das Sie verwenden möchten. Wenn Sie einen relativen Pfad verwenden, wird er relativ zum Dokumentenstammverzeichnis des Standard-Virtual-Hosts sein - und das ist mit Sicherheit nicht das Dokumentenstammverzeichnis, das Sie verwenden werden, wenn Sie einen Virtual-Host mit dem FCGI einrichten möchten.
  • Das FCGI selbst kann nicht innerhalb eines Virtual-Hosts definiert werden - es muss in der Hauptserverkonfiguration definiert werden. Hier kommt das Problem der relativen Adressierung ins Spiel.
  • Die Socket-Datei muss sowohl vom FCGI-Benutzer als auch vom Apache-Benutzer lesbar und beschreibbar sein. Normalerweise tun Sie dies, indem Sie die Socket-Datei gruppenschreibbar ändern und die Gruppe dieser Socket-Datei in eine Gruppe ändern, der sowohl der Benutzer als auch der Apache angehören.

Hier ist der Konfigurationsausschnitt, den Sie zu Ihrer httpd.conf hinzufügen müssen. Ich verwende die gleichen Verzeichnisse wie im lighttpd-Beispiel, Sie werden dies wahrscheinlich an Ihre Situation anpassen müssen.


FastCgiExternalServer /home/gb/work/myproject/publichtml/admin.fcgi -host 127.0.0.1:8000
FastCgiExternalServer /home/gb/work/myproject/publichtml/main.fcgi -host 127.0.0.1:8001

<VirtualHost *>
ServerAdmin gb@bofh.ms
Servername www.example.com
ErrorLog /home/gb/work/myproject/logs/django-error.log
CustomLog /home/gb/work/myproject/logs/django-access.log combined
DocumentRoot /home/gb/work/myproject/public_html
RewriteEngine On
RewriteRule ^(/admin/.)$ /admin.fcgi$1 [L]
RewriteRule ^(/main/.)$ /main.fcgi$1 [L]
</VirtualHost> ```

Sie müssen dem Webserver Schreibzugriff auf das Log-Verzeichnis ermöglichen, daher möchten Sie möglicherweise einen anderen Ort dafür verwenden - möglicherweise in `/var/log/apache/` oder wo auch immer Ihr Apache seine Logs hinstellt. Die FastCgiExternalServer-Direktiven müssen außerhalb der Virtual-Host-Definitionen stehen, müssen aber auf Dateien innerhalb des Dokumentenstammverzeichnisses der Virtual Hosts verweisen. Diese Dateien müssen jedoch (und sollten wahrscheinlich nicht) im Dateisystem existieren, sie sind rein virtuell. Die gegebene Einrichtung spiegelt die Einrichtung wider, die ich für das lighttpd-Szenario vorgenommen habe.

Starten Sie nun Ihren Apache neu, starten Sie Ihr django-fcgi.py und Sie sollten auf Ihre Django-Anwendung zugreifen können. Vergessen Sie nicht, die admin_media-Dateien in das Dokumentenstammverzeichnis zu kopieren, sonst wird Ihr Admin sehr hässlich aussehen.

django-fcgi.py --settings=myproject.settings.main --host=127.0.0.1 --port=8000 --daemon django-fcgi.py --settings=myproject.settings.admin --host=127.0.0.1 --port=8001 --daemon


Viel Spaß.

Mal wieder neues bei Django

Neues gibts da ja dauernd, aber diesmal wieder ein sehr interessantes Feature: das inspectdb Kommando liefert nämlich aus einer PostgreSQL Datenbank die ganzen Tabellen und Felder im Format eines Python Datenmodells. Zusätzlich werden - sofern in der Datenbank abgelegt - auch Fremdschlüssel gefunden. Sehr praktisch wenn man eine Oberfläche für eine bestehende Datenbank bauen muss, man spart sich viel Tipparbeit.

Einfacher Dateisystem-Browser mit Django schreiben

Dieser Artikel ist mal wieder in Englisch, da er auch für die Leute auf #django interessant sein könnte. Dieser Beitrag zeigt, wie man einen sehr einfachen Dateisystem-Browser mit Django erstellt. Dieser Dateisystem-Browser verhält sich größtenteils wie ein statischer Webserver, der die Verzeichnisnavigation ermöglicht. Die einzige Besonderheit ist, dass Sie das Django-Admin verwenden können, um Dateisysteme zu definieren, die in den Namensraum des Django-Servers eingebunden werden. Dies dient nur zur Demonstration, wie eine Django-Anwendung verschiedene Datenquellen neben der Datenbank nutzen kann. Es ist nicht wirklich dazu gedacht, statischen Inhalt zu servieren (obwohl es mit hinzugefügter Authentifizierung quite nützlich für eingeschränkten statischen Inhalt sein könnte!).

Auch wenn die Anwendung sehr einfache Sicherheitsprüfungen an den übergebenen Dateinamen durchführt, sollten Sie dies nicht auf einem öffentlichen Server ausführen - ich habe keine Sicherheitstests durchgeführt und es könnte buttloads von schlechten Dingen geben, die Ihre privaten Daten der Welt preisgeben könnten. Sie wurden gewarnt. Wir beginnen wie üblich mit der Erstellung der Dateisystem-Anwendung mit dem Befehl django-admin.py startapp filesystems. Machen Sie es einfach so, wie Sie es mit Ihrer Umfrageanwendung im ersten Tutorial gemacht haben. Nur zur Orientierung, so sieht die myproject-Verzeichnis auf meiner Entwicklungsmaschine aus:


.
|-- apps
| |-- filesystems
| | |-- models
| | |-- urls
| | `-- views
| `-- polls
| |-- models
| |-- urls
| `-- views
|-- public_html
| `-- admin_media
| |-- css
| |-- img
| | `-- admin
| `-- js
| `-- admin
|-- settings
| `-- urls
`-- templates
 `-- filesystems

Nach der Erstellung der Infrastruktur beginnen wir mit dem Aufbau des Modells. Das Modell für die Dateisysteme ist sehr einfach - nur ein Name für das Dateisystem und ein Pfad, an dem die Dateien tatsächlich gespeichert sind. Hier ist es also, das Modell:


 from django.core import meta

class Filesystem(meta.Model):

fields = ( meta.CharField('name', 'Name', maxlength=64), meta.CharField('path', 'Path', maxlength=200), )

def repr(self): return self.name

def get_absolute_url(self): return '/files/%s/' % self.name

def isdir(self, path): import os p = os.path.realpath(os.path.join(self.path, path)) if not p.startswith(self.path): raise ValueError(path) return os.path.isdir(p)

def files(self, path=''): import os import mimetypes p = os.path.realpath(os.path.join(self.path, path)) if not p.startswith(self.path): raise ValueError(path) l = os.listdir(p) if path: l.insert(0, '..') return [(f, os.path.isdir(os.path.join(p, f)), mimetypes.guess_type(f)[0] or 'application/octetstream') for f in l]

def file(self, path): import os import mimetypes p = os.path.realpath(os.path.join(self.path, path)) if p.startswith(self.path): (t, e) = mimetypes.guess_type(p) return (p, t or 'application/octetstream') else: raise ValueError(path)

admin = meta.Admin( fields = ( (None, {'fields': ('name', 'path')}), ), list_display = ('name', 'path'), search_fields = ('name', 'path'), ordering = ['name'], )


Wie Sie sehen können, ist das Modell und das Admin eher langweilig. Was interessant ist, sind jedoch die zusätzlichen Methoden `isdir`, `files` und `file`. `isdir` überprüft, ob ein gegebener Pfad unter dem Dateisystem ein Verzeichnis ist oder nicht. `files` gibt die Dateien des angegebenen Pfades unter dem Basispfad des Dateisystems zurück und `file` gibt den echten Dateipfad und den MIME-Typ einer gegebenen Datei unter dem Basispfad des Dateisystems zurück. Alle drei Methoden überprüfen die Gültigkeit des übergebenen Pfades - wenn der resultierende Pfad nicht unter dem Basispfad des Dateisystems liegt, wird eine ValueError ausgelöst. Dies soll sicherstellen, dass niemand `..` im Pfadnamen verwendet, um aus dem definierten Dateisystem-Bereich auszubrechen. Das Modell enthält also spezielle Methoden, die Sie verwenden können, um auf den Inhalt des Dateisystems selbst zuzugreifen, ohne sich Gedanken darüber zu machen, wie dies in Ihren Ansichten zu tun ist. Es ist die Aufgabe des Modells, solche Dinge zu kennen.

Der nächste Teil Ihres kleinen Dateisystem-Browsers wird die URL-Konfiguration sein. Sie ist eher einfach, sie besteht aus der Zeile in `settings/urls/main.py` und dem Modul `myproject.apps.filesystems.urls.filesystems`. Zuerst die Zeile im Haupt-URLs-Modul:

from django.conf.urls.defaults import *

urlpatterns = patterns('', (r'^files/', include('myproject.apps.filesystems.urls.filesystems')), )


Als nächstes das eigene URLs-Modul der Dateisysteme:

from django.conf.urls.defaults import *

urlpatterns = patterns('myproject.apps.filesystems.views.filesystems', (r'^$', 'index'), (r'^(?P.?)/(?P.)$', 'directory'), )


Sie können die Anwendung nun der Haupt-Einstellungsdatei hinzufügen, damit Sie es später nicht vergessen. Suchen Sie einfach nach der Einstellung INSTALLED_APPS und fügen Sie den Dateibrowser hinzu:

INSTALLED_APPS = ( 'myproject.apps.polls', 'myproject.apps.filesystems' )


Ein Teil fehlt noch: die Ansichten. Dieses Modul definiert die extern erreichbaren Methoden, die wir im URL-Mapper definiert haben. Wir benötigen also zwei Methoden, `index` und `directory`. Die zweite funktioniert tatsächlich nicht nur mit Verzeichnissen - wenn sie eine Datei übergeben bekommt, präsentiert sie einfach den Inhalt dieser Datei mit dem richtigen MIME-Typ. Die Ansicht macht Gebrauch von den in dem Modell definierten Methoden, um auf den tatsächlichen Dateisysteminhalt zuzugreifen. Hier ist der Quellcode für das Ansichtsmodul:

from django.core import template_loader from django.core.extensions import DjangoContext as Context from django.core.exceptions import Http404 from django.models.filesystems import filesystems from django.utils.httpwrappers import HttpResponse

def index(request): fslist = filesystems.getlist(orderby=['name']) t = templateloader.gettemplate('filesystems/index') c = Context(request, { 'fslist': fslist, }) return HttpResponse(t.render(c))

def directory(request, filesystem_name, path): import os try: fs = filesystems.getobject(name exact=filesystemname) if fs.isdir(path): files = fs.files(path) tpl = templateloader.gettemplate('filesystems/directory') c = Context(request, { 'dlist': [f for (f, d, t) in files if d], 'flist': [{'name':f, 'type':t} for (f, d, t) in files if not d], 'path': path, 'fs': fs, }) return HttpResponse(tpl.render(c)) else: (f, mimetype) = fs.file(path) return HttpResponse(open(f).read(), mimetype=mimetype) except ValueError: raise Http404 except filesystems.FilesystemDoesNotExist: raise Http404 except IOError: raise Http404


Sehen Sie, wie die Elemente des Verzeichnismusters als Parameter an die Directory-Methode übergeben werden - der Dateisystemname wird verwendet, um das richtige Dateisystem zu finden, und der Pfad wird verwendet, um den Inhalt unter dem Basispfad dieses Dateisystems zuzugreifen. MIME-Typen werden mit dem mimetypes-Modul aus der Python-Distribution ermittelt, übrigens.

Der letzte Teil unseres kleinen Tutorials sind die Vorlagen. Wir benötigen zwei Vorlagen - eine für den Index der definierten Dateisysteme und eine für den Inhalt eines Pfades unter einem Dateisystem. Wir benötigen keine Vorlage für den Dateiinhalt - der Dateiinhalt wird roh geliefert. Zuerst also die Hauptindexvorlage:

{% if fslist %}

definierte Dateisysteme

{% else %}

Entschuldigung, es wurden keine Dateisysteme definiert.

{% endif %}

Die andere Vorlage ist die Verzeichnisvorlage, die den Inhalt eines Pfades unter dem Basispfad des Dateisystems anzeigt:

{% if dlist or flist %}

Dateien in //{{ fs.name }}/{{ path }}

    {% for d in dlist %}
  • {{ d }}
  • {% endfor %} {% for f in flist %}
  • {{ f.name }} ({{ f.type }})
  • {% endfor %}
{% endif %}


Beide Vorlagen müssen irgendwo in Ihrem TEMPLATE-PFAD gespeichert werden. Ich habe einen Pfad im TEMPLATE-PFAD mit dem Namen der Anwendung eingerichtet: `filesystems`. Dort habe ich die Dateien als `index.html` und `directory.html` gespeichert. Natürlich würden Sie normalerweise eine Basisvorlage für die Website erstellen und diese in Ihren normalen Vorlagen erweitern. Und Sie würden eine `404.html` hinzufügen, um 404-Fehler zu behandeln. Aber das bleibt als Übung für den Leser. Nachdem Sie Ihren Entwicklungsserver für Ihr Admin gestartet haben (vergessen Sie nicht, DJANGO SETTINGS MODULE entsprechend einzustellen!), können Sie ein Dateisystem zu Ihrer Datenbank hinzufügen (haben Sie irgendwann zwischenzeitlich `django-admin.py install filesystems` gemacht? Nein? Machen Sie es jetzt, bevor Sie Ihren Server starten). Jetzt stoppen Sie den Admin-Server, ändern Sie Ihr DJANGO SETTINGS MODULE und starten Sie den Haupt-Einstellungsserver. Jetzt können Sie zu [http://localhost:8000/files/](http://localhost:8000/files/) surfen (zumindest wenn Sie Ihre URLs und Ihren Server so eingerichtet haben wie ich) und die Dateien in Ihrem Dateisystem durchsuchen. Das ist alles. War nicht sehr kompliziert, oder? Django ist wirklich einfach zu verwenden.

Leichen im Keller

Jede Software hat sie - irgendwelche Leichen im Keller die anfangen zu stinken wenn man sie findet. Django leider auch. Und zwar die Behandlung von Unicode. Der automatisch generierte Admin in Django schickt immer XHTML und utf-8 raus an den Browser. Die Browser schicken daher auch utf-8 zurück. Jetzt gibt es aber Browser die bei solchen Sachen dann ein etwas anderes Format für die zu schickenden Daten benutzen - das sogenannte Multipart-Format. Dieses wird verwendet weil es die einzige garantierte Methode in HTTP-POST ist, bei der man einen Zeichensatz mitschicken kann.

Dummerweise parsed Django diese Multipart-POSTs mit dem email Modul von Python. Dieses produziert dann fleissig Unicode-Strings aus den als utf-8 markierten Parts. Was ja auch an und für sich korrekt ist - nur sind im Django-Source überall im Sourcecode str() Aufrufe verstreut. Und die krachen dann natürlich, wenn sie unicode vorgeworfen bekommen in dem Zeichen oberhalb von chr(128) drin sind.

Ich hab mir den Source mal angeguckt, der realistischste Ansatz dürfte sein in Django einfach generell dafür zu sorgen das auch Unicode-Ergebnisse dann wieder nach utf-8 gewandelt werden, so das intern nur normale Python-Strings benutzt werden. Das klappt auch soweit, aber es gibt dann noch Probleme mit manchen Datenbanken die bei Speicherung von utf-8 Inhalten das erkennen und dann beim Lesen der Inhalte wieder Unicode produzieren - SQLite ist so eine Datenbank.

Tja, das wird nicht ganz einfach zu beheben sein. Ich hab mich schon mal dran versucht, das ist ein ziemlich ekliges Thema und leider in Django überhaupt nicht berücksichtigt worden - und daher kracht es an allen Ecken und Enden. Mal gucken ob ich da nicht doch noch was brauchbares hinkriege ...

Was mir auch noch aufgefallen ist: Django schickt den Content-type nur über ein meta-Tag mit http-equiv raus. Das ist ein übler Hack, wesentlich besser wäre es wenn der Content-type korrekt als Header gesetzt wäre, dann kann auch nix schief gehen wenn z.B. Apache einen Default-Charset zufügen will. Und die Browser würden auch wesentlich reproduzierbarer reagieren.

Jedenfalls ist das wieder der typische Fall von amerikanischen Programmierern. Die erzählen einem gerne das man einfach nur auf Unicode und utf-8 wechseln soll wenn man von seinen Zeichensatzkodierungsproblemen berichtet, aber ich habe bisher noch keine Software eines amerikanischen Programmierers gesehen die Unicode korrekt gehandhabt hätte ...

Ansonsten gibts in Django noch so die eine oder andere Klinke - besonders nervig, weil nicht dokumentiert, aber leicht zu lösen: die Standard-Zeitzone in Django ist America/Chicago. Dazu muss man dann nur eine Variable TIME_ZONE mit 'Europe/Berlin' als Wert in sein settings-File schreiben und noch einen kleinen Patch anwenden, damit Django mit dem '-' als Zeitzonentrennzeichen klarkommt. Oh Mann, wenn Amerikaner schon mal Software schreiben ...

Irgendwie steigt im Moment gerade meine Motivation mir doch erstmal Ruby on Rails genauer anzugucken, schliesslich sind das Dänen die damit angefangen haben und die sollten zumindestens solche Sachen richtig hinkriegen (wenn nur nicht dieser nette automatische Administrationsteil von Django wäre - der ist es ja genau auf den ich es abgesehen hätte. Warum hat sowas nur keiner für ROR eingebaut, menno ...)

Update: Ich hab am entsprechenden Ticket zum Unicode-Problem einen Patch angehängt (einfach nach ganz unten scrollen) der erstmal das Problem halbwegs in den Griff bekommt - sofern man kein SQLite einsetzt, da SQLite immer Unicode-Strings zurückliefert und die dann auch wieder Stress machen. Aber zumindestens mit PostgreSQL funktionieren jetzt Umlaute in Django. Die Lösung ist nicht wirklich perfekt, aber zumindestens mit nur wenig Codeänderung reinzubringen. Eine richtige Lösung würde wohl grössere Codeumbauten erfordern.

Ein weiterer Patch hängt am Ticket zum Zeitzonenproblem, mit dem Patch kann man dann auch TIME_ZONE = 'Europe/Berlin' benutzen um die Zeitangaben zum Beispiel in der Änderungshistorie in der richtigen Zeitzone zu bekommen.

In solchen Momenten wünscht man sich commit-Rechte zu Django, um solche recht überschaubaren Patches selber reinstellen zu können

Noch ein Update: Adrian war im Chat gestern und heute und die Probleme mit Unicode sind weitestgehend raus. Nur mit SQLite gibts noch Stress, aber da hab ich den Patch schon fertig. Und die Zeitzonengeschichte ist auch behoben im SVN. Und er hat Unittests begonnen. Sehr sinnvoll, wenn man dann mal auf Dauer das ganze Framework sauber durchtesten kann nach einem Patch ...

Django, lighttpd und FCGI, zweiter Versuch

In meinem ersten Versuch mit diesem Zeug habe ich ein Beispiel gegeben, wie man Django-Projekte hinter lighttpd mit einfachen FCGI-Skripten, die in den Server integriert sind, ausführen kann. Ich werde ein wenig mehr über dieses Zeug erzählen, mit einer Möglichkeit, lighttpd und Django zu kombinieren, die viel mehr Flexibilität bei der Verteilung von Django-Anwendungen über Maschinen bietet. Dies ist besonders wichtig, wenn Sie mit hohen Lasten auf Ihren Servern rechnen. Natürlich sollten Sie den Django-Caching-Middleware verwenden, aber es gibt Zeiten, in denen selbst das nicht ausreicht und die einzige Lösung darin besteht, mehr Hardware an das Problem zu werfen.

Aktualisierung: Ich pflege meine Beschreibungen jetzt in meinem Trac-System. Siehe die lighty+FCGI-Beschreibung für Django.

Hinweis: Da Django sehr neue Software ist, habe ich keine Produktionserfahrungen damit. Daher handelt es sich hier eher um einen theoretischen Standpunkt, in den Wissen einfließt, das ich durch den Betrieb von Produktionssystemen für mehrere größere Portale gewonnen habe. Am Ende kommt es nicht so sehr darauf an, welche Software Sie verwenden - es kommt nur darauf an, wie Sie sie über Ihren Server-Farm verteilen können.

Um dieser Dokumentation zu folgen, benötigen Sie die folgenden Pakete und Dateien, die auf Ihrem System installiert sind:

  • [Django][2] selbst - derzeit aus dem SVN geholt. Folgen Sie den Setup-Anweisungen oder verwenden Sie python setup.py install.
  • [Flup][3] - ein Paket mit verschiedenen Möglichkeiten, WSGI-Anwendungen auszuführen. In dieser Dokumentation verwende ich den threaded WSGIServer.
  • [lighttpd][4] selbstverständlich. Sie benötigen mindestens die FastCGI-, die Rewrite- und die Accesslog-Module, diese werden in der Regel mit dem System kompiliert.
  • [Eunuchs][5] - nur erforderlich, wenn Sie Python 2.3 verwenden, da Flup socketpair in den vorkompilierten Servern verwendet und dies erst ab Python 2.4 verfügbar ist.
  • [django-fcgi.py][6] - mein FCGI-Server-Skript, das eines Tages Teil der Django-Distribution sein könnte, aber für den Moment einfach hier herunterladen. Legen Sie dieses Skript irgendwo in Ihren $PATH, z.B. /usr/local/bin und machen Sie es ausführbar.
  • Wenn das oben genannte aus irgendeinem Grund nicht funktioniert (vielleicht unterstützt Ihr System socketpair nicht und kann daher den vorkompilierten Server nicht verwenden), können Sie [django-fcgi-threaded.py][7] - eine Alternative, die den Threading-Server mit all seinen Problemen verwendet. Ich verwende es zum Beispiel auf Mac OS X für die Entwicklung.

Bevor wir beginnen, lassen Sie uns ein wenig über Serverarchitektur, Python und hohe Last sprechen. Die noch bevorzugte Installation von Django erfolgt hinter Apache2 mit mod python2. mod python2 ist eine recht leistungsfähige Erweiterung für Apache, die einen vollständigen Python-Interpreter (oder sogar viele Interpreter mit unterschiedlichen Namensräumen) in den Apache-Prozess integriert. Dies ermöglicht es Python, viele Aspekte des Servers zu steuern. Aber es hat einen Nachteil: Wenn der einzige Zweck darin besteht, Anfragen von Benutzern an die Anwendung weiterzuleiten, ist es eine ziemliche Übertreibung: Jeder Apache-Prozess oder Thread wird einen vollständigen Python-Interpreter mit Stack, Heap und allen geladenen Modulen enthalten. Apache-Prozesse werden auf diese Weise etwas fett.

Ein weiterer Nachteil: Apache ist einer der flexibelsten Server da draußen, aber im Vergleich zu kleinen Servern wie lighttpd ein Ressourcenfresser. Und - aufgrund der Architektur der Apache-Module - wird mod_python die vollständige Anwendung im Sicherheitskontext des Webservers ausführen. Zwei Dinge, die Sie in Produktionsumgebungen nicht oft mögen.

Ein natürlicher Ansatz ist also die Verwendung leichterer HTTP-Server und das Hinterlegen Ihrer Anwendung dahinter - unter Verwendung des HTTP-Servers selbst nur zum Servieren von Medien und unter Verwendung von FastCGI, um Anfragen vom Benutzer an Ihre Anwendung weiterzuleiten. Manchmal stellen Sie diesen kleinen HTTP-Server hinter einen Apache-Front, der nur mod_proxy (entweder direkt oder über mod_rewrite) verwendet, um Anfragen an den Webserver Ihrer Anwendung weiterzuleiten - und glauben Sie es oder nicht, dies ist tatsächlich viel schneller als das Servieren der Anwendung direkt mit Apache!

Die zweite Falle ist Python selbst. Python hat eine recht schöne Threading-Bibliothek. Es wäre also ideal, Ihre Anwendung als Thread-Server zu erstellen - weil Threads viel weniger Ressourcen als Prozesse verbrauchen. Aber das wird Sie beißen, wegen einer speziellen Funktion von Python: der GIL. Der gefürchtete globale Interpreter-Lock. Dies ist kein Problem, wenn Ihre Anwendung zu 100% Python ist - der GIL greift nur, wenn interne Funktionen verwendet werden oder wenn C-Erweiterungen verwendet werden. Schade, dass fast alle DBAPI-Bibliotheken mindestens einige Datenbank-Client-Code verwenden, der eine C-Erweiterung verwendet - Sie starten einen SQL-Befehl und das Threading wird deaktiviert, bis der Aufruf zurückkehrt. Keine mehreren Abfragen laufen ...

Die bessere Option ist also die Verwendung eines Fork-Servers, weil der GIL dann nicht greift. Dies ermöglicht einem Fork-Server, mehrere Prozessoren in Ihrer Maschine effizient zu nutzen - und so auf lange Sicht viel schneller zu sein, trotz des Overheads von Prozessen gegenüber Threads.

Für diese Dokumentation nehme ich einen dreischichtigen Ansatz zur Verteilung der Software: Die Front wird Ihr vertrauenswürdiger Apache sein, der einfach alles an Ihren projektspezifischen lighttpd weiterleitet. Der lighttpd hat Zugriff auf das Dokumentenstammverzeichnis Ihres Projekts und leitet spezielle Anfragen an Ihren FCGI-Server weiter. Der FCGI-Server selbst kann auf einem anderen Rechner laufen, wenn dies für die Lastverteilung erforderlich ist. Er wird einen vorkompilierten Server verwenden, weil es in Python ein Threading-Problem gibt, und er kann Multiprozessor-Maschinen nutzen.

Ich werde nicht viel über die erste Ebene sprechen, weil Sie dies leicht selbst einrichten können. Leiten Sie einfach Dinge an die Maschine weiter, auf der Ihr lighttpd läuft (in meinem Fall läuft der Apache normalerweise auf anderen Maschinen als die Anwendungen). Schauen Sie in der mod_proxy-Dokumentation nach, normalerweise ist es nur ProxyPass und ProxyPassReverse.

Die zweite Ebene ist interessanter. lighttpd ist ein bisschen seltsam in der Konfiguration von FCGI-Dingen - Sie benötigen FCGI-Skripte im Dateisystem und müssen diese mit Ihrem FCGI-Serverprozess verbinden. Die FCGI-Skripte müssen tatsächlich keinen Inhalt enthalten - sie müssen nur im Dateisystem vorhanden sein.

Wir beginnen also mit Ihrem Django-Projektverzeichnis. Legen Sie einfach ein Verzeichnis public_html dort hinein. Das ist der Ort, an dem Sie Ihre Mediendateien ablegen, z.B. das Admin-Media-Verzeichnis. Dieses Verzeichnis wird das Dokumentenstammverzeichnis für Ihren Projekt-Server sein. Stellen Sie sicher, dass Sie nur Dateien dort ablegen, die keine privaten Daten enthalten - private Daten wie Konfigurationen und Module sollten besser an Orten bleiben, die nicht vom Webserver zugänglich sind. Als Nächstes richten Sie eine lighttpd-Konfigurationsdatei ein. Sie werden nur die Rewrite- und die FastCGI-Module verwenden. Keine Notwendigkeit, ein Zugriffsprotokoll zu führen, dies wird von Ihrer ersten Ebene, Ihrem Apache-Server, geschrieben. In meinem Fall befindet sich das Projekt in /home/gb/work/myproject - Sie müssen dies an Ihre eigene Situation anpassen. Speichern Sie den folgenden Inhalt als /home/gb/work/myproject/lighttpd.conf


 server.modules = ( "mod_rewrite", "mod_fastcgi" )
 server.document-root = "/home/gb/work/myproject/public_html"
 server.indexfiles = ( "index.html", "index.htm" )
 server.port = 8000
 server.bind = "127.0.0.1"
 server.errorlog = "/home/gb/work/myproject/error.log"

fastcgi.server = (
"/main.fcgi" => (
"main" => (
"socket" => "/home/gb/work/myproject/main.socket"
 )
 ),
"/admin.fcgi" => (
"admin" => (
"socket" => "/home/gb/work/myproject/admin.socket"
 )
 )
 )

url.rewrite = (
"^(/admin/.*)$" => "/admin.fcgi$1",
"^(/polls/.*)$" => "/main.fcgi$1"
 )

mimetype.assign = (
".pdf" => "application/pdf",
".sig" => "application/pgp-signature",
".spl" => "application/futuresplash",
".class" => "application/octet-stream",
".ps" => "application/postscript",
".torrent" => "application/x-bittorrent",
".dvi" => "application/x-dvi",
".gz" => "application/x-gzip",
".pac" => "application/x-ns-proxy-autoconfig",
".swf" => "application/x-shockwave-flash",
".tar.gz" => "application/x-tgz",
".tgz" => "application/x-tgz",
".tar" => "application/x-tar",
".zip" => "application/zip",
".mp3" => "audio/mpeg",
".m3u" => "audio/x-mpegurl",
".wma" => "audio/x-ms-wma",
".wax" => "audio/x-ms-wax",
".ogg" => "audio/x-wav",
".wav" => "audio/x-wav",
".gif" => "image/gif",
".jpg" => "image/jpeg",
".jpeg" => "image/jpeg",
".png" => "image/png",
".xbm" => "image/x-xbitmap",
".xpm" => "image/x-xpixmap",
".xwd" => "image/x-xwindowdump",
".css" => "text/css",
".html" => "text/html",
".htm" => "text/html",
".js" => "text/javascript",
".asc" => "text/plain",
".c" => "text/plain",
".conf" => "text/plain",
".text" => "text/plain",
".txt" => "text/plain",
".dtd" => "text/xml",
".xml" => "text/xml",
".mpeg" => "video/mpeg",
".mpg" => "video/mpeg",
".mov" => "video/quicktime",
".qt" => "video/quicktime",
".avi" => "video/x-msvideo",
".asf" => "video/x-ms-asf",
".asx" => "video/x-ms-asf",
".wmv" => "video/x-ms-wmv"
 )

Ich binde den lighttpd nur an die localhost-Schnittstelle, weil in meiner Testumgebung der lighttpd auf demselben Host wie der Apache-Server läuft. In Multi-Server-Einstellungen werden Sie den lighttpd-Servern natürlich an die öffentliche Schnittstelle binden. Die FCGI-Skripte kommunizieren in dieser Einstellung über Sockets, weil in dieser Testumgebung ich nur einen Server für alles verwende. Wenn Ihre Maschinen verteilt wären, würden Sie die "host" und "port" Einstellungen anstelle der "socket" Einstellung verwenden, um mit FCGI-Servern auf verschiedenen Maschinen zu verbinden. Und Sie würden mehrere Einträge für die "main" Sache hinzufügen, um die Last der Anwendung auf mehrere Maschinen zu verteilen. Schauen Sie in der lighttpd-Dokumentation nach, welche Optionen Sie haben werden.

Ich richte zwei FCGI-Server für dies ein - einen für die Admin-Einstellungen und einen für die Haupt-Einstellungen. Alle Anwendungen werden durch die Haupt-Einstellungen FCGI weitergeleitet und alle Admin-Anfragen werden an den Admin-Server geleitet. Dies geschieht mit den beiden Rewrite-Regeln - Sie müssen eine Rewrite-Regel für jede Anwendung hinzufügen, die Sie verwenden.

Da lighttpd die FCGI-Skripte benötigt, um sie zu existieren, um den PATH_INFO an das FastCGI weiterzugeben, müssen Sie die folgenden Dateien berühren: /home/gb/work/myprojectg/public_html/admin.fcgi ``/home/gb/work/myprojectg/public_html/main.fcgi

Sie müssen keinen Code enthalten, sie müssen nur im Verzeichnis aufgeführt sein. Ab lighttpd 1.3.16 (zum Zeitpunkt der Abfassung dieser Zeilen nur in svn) können Sie ohne die Stubs für die .fcgi laufen - Sie fügen einfach "check-local" => "disable" zu den beiden FCGI-Einstellungen hinzu. Dann sind die lokalen Dateien nicht mehr erforderlich. Wenn Sie also diese Konfigurationsdatei erweitern möchten, müssen Sie nur einige sehr grundlegende Regeln beachten:

  • jede Einstellungsdatei benötigt ihren eigenen .fcgi-Handler
  • jeder .fcgi muss im Dateisystem berührt werden - dies könnte in einer zukünftigen Version von lighttpd verschwinden, aber für den Moment ist es erforderlich
  • die Lastverteilung erfolgt auf Ebene der .fcgi - fügen Sie mehrere Server oder Sockets hinzu, um die Last auf mehrere FCGI-Server zu verteilen
  • jede Anwendung benötigt eine Rewrite-Regel, die die Anwendung mit dem .fcgi-Handler verbindet

Jetzt müssen wir die FCGI-Server starten. Das ist eigentlich ganz einfach, verwenden Sie einfach das bereitgestellte django-fcgi.py-Skript wie folgt:


 django-fcgi.py --settings=myproject.work.main
 --socket=/home/gb/work/myproject/main.socket
 --minspare=5 --maxspare=10 --maxchildren=100
 --daemon

django-fcgi.py --settings=myproject.work.admin
 --socket=/home/gb/work/myproject/admin.socket
 --maxspare=2 --daemon

Diese beiden Befehle starten zwei FCGI-Serverprozesse, die die angegebenen Sockets zur Kommunikation verwenden. Der Admin-Server wird nur zwei Prozesse verwenden - dies liegt daran, dass der Admin-Server oft nicht der Server mit den vielen Hits ist, das ist der Hauptserver. Daher erhält der Hauptserver eine höhere als Standard-Einstellung für Reserveprozesse und maximale Kindprozesse. Natürlich ist dies nur ein Beispiel - passen Sie es an Ihre Bedürfnisse an.

Der letzte Schritt ist das Starten Ihres lighttpd mit Ihrer Konfigurationsdatei: lighttpd -f /home/gb/work/myproject/lighttpd.conf

Das war's. Wenn Sie jetzt entweder den lighttpd direkt unter http://localhost:8000/polls/ oder durch Ihren Front-Apache zugreifen, sollten Sie die Ausgabe Ihrer Anwendung sehen. Zumindest, wenn alles richtig gelaufen ist und ich nicht zu viele Fehler gemacht habe.

es gibt Tage da hasst mein Computer mich

Zum Beispiel wenn ich mit Flup spiele und statt des threaded Servers einen forked Server nehmen will. Und feststelle, das der dann aber die Funktion socketpair benötigt. Die aber dummerweise nur ab Python 2.4 verfügbar ist, welches zwar auf Debian Sarge da ist, aber dafür gibts in der Debian Sarge für Python 2.4 keinen Psycopg - welcher wiederum Voraussetzung für Django und PostgreSQL ist, weshalb ich mich überhaupt ja nur mit FastCGI beschäftige. PsycoPG selber installieren macht keinen Spaß, da man dafür nicht nur die PostgreSQL Header braucht, die normal installiert werden, sondern auch ein paar interne Header - also im Prinzip einen Build-Tree. Und dann braucht man noch die egenix-mx-base Header, die man nur für Python 2.3 kriegt, also müsste man das auch selber installieren. Backports aus der nächsten Debian geht auch nicht, da die gerade auf PostgreSQL 8.0 umbauen und Sarge ja noch 7.4 benutzt und ich nicht gleich das ganze System upgraden wollte. Und so dreht man sich im Kreis und kommt sich leicht verarscht vor vor lauter Abhängigkeiten und Versionskonflikten.

Und was macht man also als Lösung, weil der threaded Server dummerweise nur Segfaults im Psycopg produziert? Man nimmt den threaded Server, verbietet ihm das threaden und startet ihn über den spawn-fcgi vom lighttpd, oder direkt vom lighttpd. Was aber irgendwie auch wieder dämlich ist, da dann immer pro FCGI-Server 3 Threads rumgammeln, von denen 2 nur in der Prozessliste stehen und nix zu tun haben. Und das alles nur weil mod python2 (was für Django gebraucht wird) Apache2 voraussetzt, der wiederum mod perl2 voraussetzt, welches inkompatibel zum alten mod perl ist, weshalb bei mir eine ganze Reihe von meinen Sites nicht mehr laufen würden, würde ich auf Apache2 umstellen. Was ich eh nicht will, weil Apache2 mit mod python arschlangsam ist. Und schon wieder verarscht worden. Ich hätte mir echt einen sinnvolleren Beruf suchen sollen.

Wer nix kapiert hat: macht nix, ist Technik, ist nicht wichtig, wollte das einfach nur mal gesagt haben.

Django mit FCGI und lighttpd ausführen

Diese Dokumentation ist für einen grösseren Kreis als nur .de gedacht, daher das ganze in Neuwestfälisch Englisch. Sorry. Update: I maintain the actually descriptions now in my trac system. See the FCGI+lighty description for Django. There are different ways to run Django on your machine. One way is only for development: use the django-admin.py runserver command as documented in the tutorial. The builtin server isn't good for production use, though. The other option is running it with mod_python. This is currently the preferred method to run Django. This posting is here to document a third way: running Django behind lighttpd with FCGI.

First you need to install the needed packages. Fetch them from their respective download address and install them or use preinstalled packages if your system provides those. You will need the following stuff:

  • [Django][2] selbst - derzeit aus dem SVN. Folgen Sie den Setup-Anweisungen oder verwenden Sie python setup.py install.
  • [Flup][3] - ein Paket mit verschiedenen Möglichkeiten, WSGI-Anwendungen auszuführen. Ich verwende den threaded WSGIServer in dieser Dokumentation.
  • [lighttpd][4] selbstverständlich. Sie benötigen mindestens die FastCGI-, die Rewrite- und die Accesslog-Module, diese sind in der Regel mit dem System kompiliert.

Nach der Installation von ligthttpd müssen Sie eine lighttpd-Konfigurationsdatei erstellen. Die hier angegebene Konfigurationsdatei ist an meine eigenen Pfade angepasst - Sie müssen diese an Ihre eigene Situation anpassen. Diese Konfigurationsdatei aktiviert einen Server auf Port 8000 auf localhost - genau wie der runserver-Befehl dies tun würde. Aber dieser Server ist ein Server für die Produktion mit mehreren FCGI-Prozessen und einer sehr schnellen Medienauslieferung.


 # lighttpd-Konfigurationsdatei
 #
 ############ Optionen, die Sie wirklich beachten müssen ####################

server.modules = ( "mod_rewrite", "mod_fastcgi", "mod_accesslog" )

server.document-root = "/home/gb/public_html/"
 server.indexfiles = ( "index.html", "index.htm", "default.htm" )

 diese Einstellungen binden den Server an dieselbe IP und denselben Port wie runserver

server.errorlog = "/home/gb/log/lighttpd-error.log"
 accesslog.filename = "/home/gb/log/lighttpd-access.log"

fastcgi.server = (
"/myproject-admin.fcgi" => (
"admin" => (
"socket" => "/tmp/myproject-admin.socket",
"bin-path" => "/home/gb/public_html/myproject-admin.fcgi",
"min-procs" => 1,
"max-procs" => 1
 )
 ),
"/myproject.fcgi" => (
"polls" => (
"socket" => "/tmp/myproject.socket",
"bin-path" => "/home/gb/public_html/myproject.fcgi"
 )
 )
 )

url.rewrite = (
"^(/admin/.*)$" => "/myproject-admin.fcgi$1",
"^(/polls/.*)$" => "/myproject.fcgi$1"
 )

Diese Konfigurationsdatei startet nur einen FCGI-Handler für Ihre Admin-Angelegenheiten und die Standardanzahl von Handlern (jeder davon mehrthreadig!) für Ihre eigene Website. Sie können diese Einstellungen mit den üblichen ligthttpd FCGI-Einstellungen feinabstimmen, sogar externe FCGI-Erzeugung und Auslagerung von FCGI-Prozessen auf einen verteilten FCGI-Cluster nutzen! Admin-Medien-Dateien müssen in Ihr lighttpd-Dokumentenverzeichnis.

Die Konfiguration funktioniert, indem alle Standard-URLs so übersetzt werden, dass sie vom FCGI-Skript für jede Einstellungsdatei behandelt werden - um weitere Anwendungen zum System hinzuzufügen, würden Sie nur die Umschreiberegel für die /polls/-Zeile duplizieren und diese in choices oder was auch immer Ihr Modul heißt ändern. Der nächste Schritt wäre die Erstellung der .fcgi-Skripte. Hier sind die beiden, die ich verwende:


 #!/bin/sh
 # dies ist myproject.fcgi - legen Sie es in Ihr Docroot

export DJANGOSETTINGSMODULE=myprojects.settings.main

/home/gb/bin/django-fcgi.py

 #!/bin/sh
 # dies ist myproject-admin.fcgi - legen Sie es in Ihr Docroot

export DJANGOSETTINGSMODULE=myprojects.settings.admin

/home/gb/bin/django-fcgi.py

Diese beiden Dateien nutzen nur ein django-fcgi.py-Skript. Dies ist nicht Teil der Django-Distribution (noch nicht - vielleicht werden sie es einbeziehen) und der Quellcode ist hier gegeben:


 #!/usr/bin/python2.3

def main():
 from flup.server.fcgi import WSGIServer
 from django.core.handlers.wsgi import WSGIHandler
 WSGIServer(WSGIHandler()).run()

if name == 'main':
 main()

Wie Sie sehen, ist es eher einfach. Es verwendet den threaded WSGIServer aus dem fcgi-Modul, aber Sie könnten genauso leicht den geforkten Server verwenden - aber da lighttpd bereits Preforking durchführt, denke ich, dass es nicht viel Sinn hat, auf der FCGI-Ebene zu forken. Dieses Skript sollte sich irgendwo in Ihrem Pfad befinden oder einfach mit vollständig qualifiziertem Pfad wie ich es tue referenziert werden. Jetzt haben Sie alle Teile zusammen. Ich habe meine lighttpd-Konfiguration in /home/gb/etc/lighttpd.conf, die .fcgi-Skripte in /home/gb/public_html und das django-fcgi.py in /home/gb/bin. Dann kann ich das ganze mit /usr/local/sbin/lighttpd -f etc/lighttpd.conf starten. Dies startet den Server, preforkt alle FCGI-Handler und trennt sich vom tty, um ein richtiger Daemon zu werden. Das Schöne daran: Dies wird nicht unter einem speziellen Systemkonto, sondern unter Ihrem normalen Benutzerkonto ausgeführt, sodass Ihre eigenen Dateibeschränkungen gelten. lighttpd+FCGI ist recht leistungsfähig und sollte Ihnen eine sehr schöne und sehr schnelle Option zum Ausführen von Django-Anwendungen bieten. Probleme:

  • Unter hoher Last stürzen einige FCGI-Prozesse ab. Ich habe zunächst die fcgi-Bibliothek verdächtigt, aber nach etwas Herumspielen (Core-Debugging) stellte sich heraus, dass es tatsächlich der psycopg auf meinem System ist, der abstürzt. Sie könnten also mehr Glück haben (es sei denn, Sie laufen auch Debian Sarge)

  • Die Leistung hinter einem Front-Apache ist nicht das, was ich erwartet hätte. Ein lighttpd mit Front-Apache und 5 Backend-FCGI-Prozessen erreicht nur 36 Anfragen pro Sekunde auf meinem Rechner, während django-admin.py runserver 45 Anfragen pro Sekunde erreicht! (immer noch schneller als mod_python über apache2: nur 27 Anfragen pro Sekunde) Updates:

  • Die Trennung der beiden FCGI-Skripte funktionierte nicht richtig. Jetzt passe ich nicht nur auf die .fcgi-Erweiterung, sondern auf den Skriptnamen an, so dass /admin/ wirklich das myproject-admin.fcgi verwendet und /polls/ wirklich das myproject.fcgi verwendet.

  • Ich habe [ein anderes Dokument online][6], das mehr Details zur Lastverteilung enthält

Und wieder mal Django

Django - das kommende Webframework für Python - hat jetzt SQLite 3 Support. Damit ist eine Installation einer Entwicklungsumgebung für Django-Projekte jetzt extrem simpel geworden: Python 2.3 oder Python 2.4 muss da sein und ansonsten noch SQLite3 und PySQLite2. Auf dem Mac ist also im Prinzip schon alles da, ausser PySQLite2 - letzteres kann man sich aber von www.pysqlite.org holen und einfach mittels sudo python setup.py install installieren. Und schon kann man mit Django loslegen und die Tutorials durcharbeiten. Kein Apache mehr nötig, kein PostgreSQL (zwar die netteste aller SQL-Datenbanken, aber trotzdem für eine Entwicklungsumgebung auf Notebook manchmal einfach Overkill) und vor allem nicht psycopg - dessen Installation leider einen fast vollständigen PostgreSQL-Source-Tree erfordert. Es gibt also keine Ausrede für Pythonistas mehr sich nicht mit Django zu beschäftigen

Erste Django Tutorials online

Die Django-Programmierer legen mit den Tutorials los. Das erste Tutorial beschäftigt sich primär mit der Erstellung des Datenbankmodells und des Grundcodes für die zu verwaltenden Objekte und das zweite Tutorial beschäftigt sich mit der automatisch generierten Administrationsoberfläche. Sehr nett, das ganze.

Das System ist natürlich stark auf Content-Erstellung und Verwaltung ausgerichtet - aber trotzdem allgemein genug, so das man es auch für anders gelagerte Inhalte nutzen kann. Die ganze Administration wird automatisch aus dem Objektmodell und einigen Hints erstellt, orientiert sich also immer an den realen Daten im System. Und die Default-Optik ist auch recht ansprechend.

Die Serverintegration geschieht einfach über mod python - also über den Apache. Was ebenfalls ein Vorteil ist, denn mod python bietet sehr hohe Performance schon von Hause aus. Und für heftigere Fälle gibts ja das Caching in Django. Ich muss sagen, was ich bisher von Django sehe gefällt mir ausgesprochen gut.

Ein wichtiger Hinweis fehlt in der Installationsanleitung: Apache2 ist zwingend nötig, und daher auch ModPython in der entsprechenden Version. Mac OS X liefert aber nur Apache 1.3 und viele andere Server haben auch nur den 1.3er Apache zur Verfügung, da hat Django also noch ein echtes Manko.

Wer übrigens auf Debian von Apache zu Apache2 upgraden will: wenn mod perl im Einsatz ist, vergesst es. Das mod perl2 für den Apache2 in der Debian Sarge ist kompletter Schrott - als ob die API-Änderungen in mod perl2 im Vergleich zum alten mod perl nicht schon nervig genug wären. Im Prinzip kriegt man damit keine Perl-Module mehr so einfach zum Laufen.

Update: Übrigens ist gerade im Subversion zu Django eine Menge Aktivität drin um die Pflicht für Apache zu beseitigen. Ein einfacher Entwicklungsserver ist schon drin, man wird also in Zukunft für erste Spielereien keinen Apache mehr benötigen. Und auch das Deployment könnte man damit auf Dauer auch auf andere Beine stellen - z.B. FCGI hinter lighttpd.

Update 2: Das dritte Tutorial ist da und beschäftigt sich mit der Sicht für den Besucher. Die haben ein ganz schön heftiges Tempo im Moment bei Django.

Django - neues Webframework für Python

Mal wieder ein weiteres Web-Framework für Python, diesmal mit dem markigen Namen Django. Ich bin zwar skeptisch was weitere Webframeworks angeht - gibt schon haufenweise, und ich muss gestehen das ich zu dem einen oder anderen auch was beigetragen habe - aber dieses bietet einige interessante Ansätze.

Zum Einen addressiert es ähnliche Lösungen wie Ruby on Rails - erwähnt Ruby on Rails aber mit keinem Wort. Das ist schon mal positiv, man hat in letzter Zeit fast den Eindruck das die Python-Programmierer wegen ROR in Panik verfallen und meinen alles müsse sich nur noch daran orientieren.

Zum Anderen bietet Django automatisch generierte Backendseiten. Das ist etwas das ich sehr mag und was ich z.B. an Zope so nett finde - man hat gleich einen Weg mit dabei mit dem man mit den eigentlichen Daten rumspielen kann, noch bevor das eigentliche Frontend steht. Sehr praktisch gerade in der ersten Entwicklungsphase.

Auch einige der anderen Ideen sind ganz witzig - zum Beispiel das Mapping von URLs zu Handlern im Python-Code über regular Expressions. Erinnert ein bischen an mod_rewrite im Apache (wobei bei solchen Lösungen immer die Frage der Priorisierung von sich überlappenden regulären Ausdrücken bleibt). Und ein integrierter object-relation-Manager ist auch nicht schlecht, auch wenn man da natürlich auch genausogut auf fertige Lösungen zurückgreifen kann. Und das die Entwickler gleich daran gedacht haben das man effiziente Cache-Systeme braucht und dabei dann auf memcached setzen ist auch nett - viele Projekte sterben irgendwann den Tod der Load, nur weil nicht rechtzeitig an Caching gedacht wurde.

Die Template-Sprache sieht allerdings etwas gewöhnungsbedürftig aus und irgendwie frage ich mich dabei schon warum es davon fast noch mehr geben muss als von Webframeworks