python - 16.10.2005 - 13.1.2006

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).

MoinMoin Release 1.5 - hui, das neue MoinMoin sieht ja richtig slick aus.

code.enthought.com - Enthought Tool Suite - recht interessant klingende GUI-Bibliothek, die auf WxPython aufsetzt und noch komfortablere Anwendungserstellung ermöglicht. Besonders interessant der Einsatz des "Traits"-Konzeptes für die automatische Erstellung von Oberflächen.

Codeville - und noch eine Versionsverwaltung, diese ist in Python geschrieben und adressiert speziell das Problem der Merge-Algorithmen.

cucumber2: an object-relational mapping system for Python and PostgreSQL - ein weiterer ORM für Python. Besonderheit hier: PostgreSQL Tabellenvererbung wird benutzt, um die Übergänge zwischen Objekten und Klassen einfacher zu gestalten. Dadurch aber auch nicht auf andere Datenbanken portierbar.

LGT: Lightweight Game Toolkit for Python - besonders interessant die NanoThreads (Coroutinen für Python), das EventNet (erweiterte Eventprogrammierung) und Gherkin (eine Alternative zu Pickle/Marshal). Zu NanoThreads und EventNet gibt es mit FibraNet jetzt auch einen erweiterten Nachfolger (der von LGT losgelöst ist).

Webstemmer - HTML-Grabber der aufgrund des Layouts den eigentlichen Kerntext von Websites extrahiert.

simple_json 1.0 - Alternative zu json.py mit weniger Macken (dafür einem Soziopathen als Programmierer - aber man kann eben nicht alles haben. In diesem Fall ist funktionierender Code jedenfalls wichtiger als freundlicher Umgangston).

Dejavu - Trac - ein weiterer ORM für Python. Dieser zeichnet sich durch absurde Klassennamen aus (Arena, Sandbox, Units ...)

appscript - Python als Alternative zu AppleScript. Also Applikations-Steuerung über die AppleScript Schnittstellen direkt aus Python Programmen heraus.

Generische Funktionen mit Python

PEAK bietet ja seit geraumer Zeit generische Funktionen ala CLOS für Python an. Ich wollte immer mal damit rumspielen, aber lange Zeit war es ja einfach nur Bestandteil von PyProtocols, und die Installation etwas haarig. Seit September diesen Jahres ist es aber ausgekoppelt und sehr viel einfacher zu installieren. Also hab ich mich mal draufgestürzt.

Und ich muss sagen: wow. Was Phillip J. Eby da geleistet hat ist wirklich fantastisch. Die Integration in Python (funktioniert ab Python 2.3 - er hat einfach eine eigene Implementierung von Dekoratoren für Python 2.3 erfunden) ist super, auch wenn natürlich das eine oder andere etwas gewöhnungsbedürftig ist.

Ein kleines Beispiel:

import dispatch

[dispatch.generic()]
def anton(a,b):
 "handle two objects"

[anton.when('isinstance(a,int) and isinstance(b,int)')]
def anton(a,b):
 return a+b

[anton.when('isinstance(a,str) and isinstance(b,str)')]
def anton(a,b):
 return a+b

[anton.when('isinstance(a,str) and isinstance(b,int)')]
def anton(a,b):
 return a*b

[anton.when('isinstance(a,int) and isinstance(b,str)')]
def anton(a,b):
 return b*a

[anton.before('True')]
def anton(a,b):
 print type(a), type(b)

Dieses kleine Beispiel liefert einfach eine Funktion namens 'anton', welche auf Basis der Parametertypen unterschiedlichen Code ausführt. Das Beispiel ist natürlich völlig sinnfrei, zeigt aber einige wichtige Eigenschaften von generischen Funktionen:

  • generische Funktionen sind - anders als klassische Objekt/Klassenmethoden - nicht an irgendwelche Klassen oder Objekte fest gebunden. Statt dessen werden sie über ihre Parametertypen ausgewählt.
  • Parametertypen müssen demnach definiert werden - im Regelfall passiert das über eine Minisprache, mit der die Bedingungen der Auswahl formuliert werden. Das ist auch der einzige syntaktische Teil der mir nicht so gut gefällt: die Bedingungen werden als String abgelegt. Allerdings ist die Integration sehr gut, man erhält saubere Syntaxfehler schon beim Laden.
  • eine generische Funktion kann mit beliebigen Bedingungen überladen werden - nicht nur der erste Parameter ist entscheidend. Bedingungen können übrigens auch auf Basis von Werten entscheidungen treffen - jeder beliebige Python-Ausdruck kann dort verwendet werden.
  • mit Methodenkombinationen (Methoden sind hier die konkreten Ausprägungen einer generischen Funktion) kann man eine Methode vor oder nach ihrem Aufruf modifizieren, ohne an den Code selber ranzukommen. Das Beispiel benutzt eine before-Methode die immer (deshalb das 'True') herangezogen wird, um Debugging-Output zu erzeugen. Natürlich kann man bei before/after Methoden jederzeit auch Bedingungen benutzen, um sich auf spezifische Ausprägungen des Aufrufs der generischen Funktion zu hängen - womit generische Funktionen ein vollwertiges Event-System sind.

Ein recht guter Artikel über RuleDispatch (das generische Funktionen Paket) gibt es bei Developerworks.

Das Beispiel zeigt übrigens die Python 2.3 Syntax für Dekoratoren. Mit Python 2.4 kann natürlich auch die @-Syntax benutzt werden. Ein Nachteil soll nicht verschwiegen werden: die Definition von generischen Funktionen und ihren Methoden ist nicht interaktiv möglich - jedenfalls nicht mit der Python 2.3 Syntax. Leider muss man da generell mit externen Definitionen in Dateien arbeiten.

RuleDispatch wird definitiv einen Platz in meinem Werkzeugkasten finden - die Syntax ist einfach genug, die Möglichkeiten hingegen sind gigantisch. Als Eventsystem schlägt es an Flexibilität jedes andere System und als generelle Möglichkeit der Strukturierung des Codes kommt es sehr nah an CLOS heran. Schade, das Django sich vermutlich auf PyDispatch ausrichten wird - RuleDispatch würde meiner Meinung nach wesentlich besser passen (da viele Aspekte in Django als Dispatch auf mehrere Parametertypen geschrieben werden könnten).

Is Rails a DSL? What is a DSL, and is it possible in Python? - Domain Specific Languages - eine recht brauchbare Beschreibung und Betrachtung der Situation in Python und Ruby.

Python Cheese Shop : python-fastcgi 1.0 - FastCGI Implementation die auf der OpenMarket FastCGI C Bibliothek aufbaut und daher deutlich schneller als reine Python-Lösungen ist.

Python OpenID 1.0.1 Released — OpenID Enabled - OpenID Client und Server in Python. Müsste ich mir mal angucken, könnte ja recht interessant für Kommentarfunktionen sein.

How-To Guide for Descriptors - eine sehr gute Erklärung, wie in Python die Properties funktionieren und was es mit den magic methods get , set und del auf sich hat (und wie getattribute da mitspielt).

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.

SystemExit und exception handlers

Immer wieder gerne genommen: SystemExit. Eine Python-Exception, die viele nicht kennen. Das besondere an dieser Exception: sie ist kein Fehler. Sie tritt auch nicht unerwartet auf. Sie wird nämlich einfach von sys.exit ausgelöst. Die Idee dahinter ist, das man so im dynamischen Ablauf eine Ende-Bearbeitung einhängen kann (z.B. irgendwelche Dateibereinigungen), ohne sich in globale Exitbearbeitung einzuklinken (mit all den Problemen die das hat).

Das Problem ist jetzt, das viele Programme und Bibliotheken einen globalen Exception-Handler installieren. Einen, der jeden Fehler abfängt und hübsch formatiert per Mail verschickt, irgendwo logged oder ähnliches. Mache ich auch ständig. Geht auch klasse - ausser wenn man in seinem Programm tatsächlich mal explizit ein vorzeitiges Ende einleiten will. Dann klappt da garnix mehr - denn man erhält entsprechende Fehler für einen Nicht-Fehler.

Besonders kritisch wird das ganze im Zusammenhang mit mehreren Prozessen. Wenn man nämlich im Laufenden Betrieb einen Prozess anstartet, will man diesen auch beenden, ohne das eventueller nachgelagerter Code ausgeführt wird. Am besten sieht man das an einem Beispielprogramm:

import signal
import os

try:
 pid = os.fork()
 if pid:
 print "Elternprozess", os.getpid()
 else:
 print "Kindprozess", os.getpid()
 sys.exit(0)
except:
 print 'Fehler aufgetreten in Prozess', os.getpid()

print "Das darf nur der Elternprozess ausführen", os.getpid()

Dieser Code hat einfach einen globalen Fehlerbehandler, der Fehler recht unspezifisch abfängt. Innerhalb des Codes wird ein paralleler Prozess mit fork gestartet. Dadurch, das SystemExit wie alle anderen Exceptions behandelt wird, wird aber der Kindprozess nicht korrekt beendet - ein Prozess kopiert den gesamten Zustand des Elternprozesses, inklusive Rücksprungadressen, offene Fehlerbehandlungen, Dateien, Datenbankverbindungen und so weiter.

Das ist natürlich fatal - denn hier wird ja sys.exit abgefangen. Es gibt also eine Fehlermeldung für den ganz normalen sys.exit(0) Aufruf. Und noch schlimmer: da SystemExit nicht extra behandelt wird, gehts danach normal weiter - und der Kindprozess rennt in Code für den Elternprozess rein. Code läuft also doppelt, was unter Umständen kritische Ergebnisse haben kann.

Wenn man den ganzen Stack an Software voll kontrollieren kann, ist die Lösung einfach:

import signal
import os

try:
 pid = os.fork()
 if pid:
 print "Elternprozess", os.getpid()
 else:
 print "Kindprozess", os.getpid()
 sys.exit(0)
except SystemExit:
 raise
except:
 print 'Fehler aufgetreten in Prozess', os.getpid()

print "Das darf nur der Elternprozess ausführen", os.getpid()

Dadurch wird einfach der SystemExit neu geworfen - also neu ausgelöst - ohne eine Meldung zu machen. Im Regelfall wird dann die Standardbehandlung von Python zuschlagen und den SystemExit in eine normale Beendigung umsetzen.

Was aber machen, wenn man mehrere gestapelte Varianten des falschen Fehlerhandlings hat? Ich hab sowas zum Beispiel bei Django und FLUP (dem FCGI/SCGI Server für Python). In Django hab ich es geändert, dann hat der Fehler im FLUP zugeschlagen. Was macht man dann?

Die Lösung ist ein wenig brutaler:

import signal
import os

try:
 pid = os.fork()
 if pid:
 print "Elternprozess", os.getpid()
 else:
 print "Kindprozess", os.getpid()
 os.kill(os.getpid(), signal.SIGTERM)
except:
 print 'Fehler aufgetreten in Prozess', os.getpid()

print "Das darf nur der Elternprozess ausführen", os.getpid()

Letzten Endes begeht der Prozess einfach Selbstmord - er schickt sich selber ein SIGTERM, also ein Beendigungssignal. Das gleich, das man normalerweise von der Shell schicken würde. Allerdings muss man dann sicherstellen, das alle eventuell nötigen Nachbereinigungen entweder schon gemacht sind, oder dann in einer SIGKILL Behandlungsroutine laufen - sonst hat man unter Umständen Problemen (z.B. sollten Datenbanktransaktionen schon commited sein).

Auch bei dieser Lösung muss man aufpassen, das nicht irgendwelche offenen Resourcen den Prozess blockieren - sonst produziert man unter Umständen Zombiprozesse. Oftmals ist es daher besser für solches Multiprozessing einen Verwaltungsprozess sehr viel früher im System zu starten - ausserhalb der Fehlerbehandlungskette - und diesen dann zu benutzen um Bearbeitungsprozesse zu starten. Allerdings hat das dann den Nachteil, das diese solcherart gestarteten Prozesse nicht die Umgebung des Elternprozess erben. Man muss daher dann in der Regel mehr Vorbereitungen treffen, um die gewünschten Aktionen auszuführen. Einen ähnlichen Ansatz verfolgt übrigens Apache - dort werden die Prozesse aus einem sehr frühen Basiszustand heraus erzeugt, so das sie möglichst Resourcenfrei daherkommen.

Vampire - Erweiterung von mod_python, mit dem es etwas Entwicklerfreundlicher wird. Zum Beispiel kann es dann auch automatischen Code-Reload.

Commentary - Postit-Kommentare für Webseiten, ausgeführt als WSGI Middleware. Sehr interessant, könnte vor allem für Sourceviews oder ähnliches interessant sein, oder für längere Texte.

pyinotify - sehr schön, endlich ein brauchbarer Wrapper für die notify-Funktion in Linux. Damit können Python-Programme sich über Änderungen an Dateisystemen informieren lassen - ideal für Verzeichnismonitoring.

akismet.py - Python-Interface für den (zentralen) Akismet Spam Scanner.

Louie - ein neues Event-Dispatching-Modul für Python. Baut auf PyDispatcher auf.

SQLAlchemy README - ein weiterer ORM für Python, orientiert sich stark an SQL und bietet einiges an magischer Syntax. Faszinierend, wie gerade in diesem Bereich die Programmierer jedes Sprachfeature versuchen zu missbrauchen nur um nicht SQL schreiben zu müssen ...

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.

Another OPML server...

Phil Pearson does it again - diesmal hat er den Community-Server für den OPML-Editor von Dave Winer in Python nachprogrammiert (damals hatte er ja den Radio Community Server nachgebaut, ein Projekt an dem ich auch zweitweilig beteiligt war). Jedenfalls kann man jetzt also auch seinen OPML-Editor auf die eigene Linux-Kiste veröffentlichen lassen, wenn man dort Python und SCGI zur Verfügung hat.

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

Weird Python 2.3 Bug

Also wirklich, manche Bugs die man jagt sind wirklich strange. Guckt euch einfach mal folgendes Python Script an:


 import re

r = re.compile('^', re.M)

src = '''<html> <head> <title>Logviewer</title> </head> <body> <div> <h1>Titel</h1> </div> <div> {{}}
 {% block content %}
 {% endblock %}
 </div> </body> </html> '''

for match in r.finditer(src):
 print match.start()

Sieht ja ganz harmlos aus - liefert einfach nur die Positionen der Newlines (ja, ich weiss, das macht man anders - der Source ist nicht von mir). Das Script hat unter Python 2.3 eine Endlosschleife auf dem letzten, abschließenden Newline. Nimmt man das raus (also pappt das """ direkt hinter das letzte Tag ohne Zeilenumbruch) funktioniert das Script. Unter Python 2.4 funktionieren beide Varianten. Und hinter sowas muss man dann herjagen ...

Ich brauch ja nicht extra zu betonen, das dieser kleine Schnipsel Code in einem grösseren Berg von Code versteckt war, oder?

Closures python,scheme,ruby - eine gute Erläuterung der etwas defekten Lookups für lexikalische Variablen in Python (jedenfalls wenn eine Zuweisung in einem Inner Scope beteiligt ist).

Routes 1.0 Released - das ist die Python-Version der URL-Routen aus Ruby-on-Rails. Sehr interessant, ich muss mich irgendwann mal dransetzen und gucken ob ich das nicht in Django als alternativen URL-Dispatcher einbauen kann.

Dejavu - Trac - ein weiterer Object-Relational-Mapper für Python. Klingt aber in Punkten ganz interessant.

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.

sql relay ist ein SQL connection pool der verschiedenste Datenbanken bedienen kann und die Verbindungen von Clients zur Datenbank über einen zentralen Pool abwickelt. Ideal in Multi-Host-Umgebungen und wenn die Connecton-Last zu hoch ist (z.B. erzeugt Django pro Request eine Connection).

coverage ist ein Tool zur Erstellung von Coverage-Übersichten - also welche Teile eines Programms ausgeführt wurden und welche nicht. Sinnvoll als Ergänzung für unittests, damit man sicher ist, das die unittests auch alle Bereiche des Codes abdecken.

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.

cucumber2 ist ein sehr interessanter Object-Relational-Mapper für Python und PostgreSQL, der unter anderem auch die Tabellenvererbung in PostgreSQL unterstützt.

Django Project - ein sehr nettes Webframework, welches ich bei mir hier einsetze.

"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.

Twisted Buch ist raus

Wer sein Hirn nicht so ohne weiteres in die verdrehte Welt von Twisted quetschen kann, dem hilft vielleicht Twisted Network Programming Essentials - ein neues Buch zu der wohl mächtigsten Internet-Protokoll-Plattfor für Python.

akaDAV - Lightweight WebDAV server and python module ist ein WebDAV Modul für Twisted. Damit kann man sich seinen eigenen WebDAV Server basteln. Könnte nütlich für mich sein, weil ich dann das Teil unter Userrechten laufen lassen kann, statt unter den Rechten des Webservers ...

python webdav server ist noch ein WebDAV Server für Python - seit 2000 nicht mehr aktualisiert, aber wenns funktioniert, könnte es reichen. Vielleicht verständlicher als Twisted-Code.

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.

Twisted Names muss ich mir mal angucken - ein DNS server in Python auf Twisted-Basis. Könnte ich ja auf DB-Benutzung umstricken, als Alternative zum PowerDNS.

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.

call of the noodle

Da schreibt einer an einem Lisp-Compiler für Python-Bytecode - sehr interessant, da man damit die Python Libraries unter einem Lisp-Dialekt benutzen könnte. Mal schauen wenn der erste Release kommt, wie der Lisp-Dialekt aussehen wird und was für Features er abdecken wird. Mit Unterstützung für Lisp-Makros wäre es sehr interessant.

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.