python

Using Django as a CMS

I am currently reworking one of my sites - Content-type: matter-transport/sentient-life-form. It was a Apache::MiniWiki based site before and is now in the transition to being a Django based site. The idea of the code for that site is to build a CMS based on Django that fully uses the Django admin. So the users should be able to do all management only in the admin, while the site itself behaves a bit like a Wiki. Autolinking, Autoediting of missing pages, Editlinks, Versioning (currently missing in the source) - all that should be done based on tools the Django admin provides.

This isn't for a full-blown site, though - the linked site is allmost empty, I never put much up there. It's more a project to dig deeper into the Django admin to see what it's like to work in it - so I know about that stuff when I start to build real projects.

The code itself is freely available - and there is already a nice thing in it. It's a template loader who pulls stuff from the database instead from the filesystem or from python eggs. It's "ticketed" at Django under #633, so it might make it into django.contrib some day.

Wie man in Python Accent-Zeichen in ihr Basiszeichen umwandelt. Ziemlich banaler Ansatz, aber für viele Zwecke völlig ausreichend. Für richtige Lösungen gibts dann PyICU ...

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.

Ajax mal anders

Subway's new Ajax framework hat einen interessanten Ansatz: per inspect wird Python-Source aus einer Methode geholt und dann nach JavaScript übersetzt. Natürlich wird dabei nur ein Subset von Python unterstützt, aber die Idee ist recht interessant - Python-Syntax für JavaScript.

Natürlich werden die semantischen Unterschiede zwischen Pythons und JavaScripts Interpreterablauf einen früher oder später beissen, aber für einfachere Sachen (und viele Ajax-Sachen sind tatsächlich recht banaler Code auf JavaScript-Seite) kann man so ohne JavaScript-Source aufbauen.

Das ganze würde natürlich wesentlich eleganter ausfallen, wenn Python eine vernüntige Makrosprache integriert hätte - bzw. wenn man in Python Makros in der Art bauen könnte, wie man es in Common Lisp kann. Damit wäre die Definition des Subsets und die Erstellung des JavaScripts daraus wesentlich eleganter machbar. Eventuell könnte hier P hillip J. Ebys Arbeit an Konfigurationssprachen helfen - es ist ja im Prinzip der Ansatz einer Makro-Einrichtung für Python.

Ich persönlich würde daher bei Python eher einen Ansatz verfolgen bei dem durch Python-Code-Ablauf (also nicht Parsen und Compilieren) JavaScript generiert wird - denn viele Ajax-Funktionalitäten sind recht standardisierte Abläufe. Es wird in der Regel der DOM-Tree manipuliert, nach festen Vorgaben, mit Daten die über JSON angeliefert werden. Das meiste liesse sich gut standardisieren. Allerdings hab ich da noch keinen konkreten Code parat, bisher ist Ajax bei mir immer noch direkter JavaScript-Code - allerdings mit Hilfe von MochiKit.

Mal schauen was sich im Ajax-Python-Land noch so tut. CrackAjax ist zumindestens mal ein weiterer Ansatz, der möglicherweise andere inspiriert das ganze etwas besser auszubauen.

WSGI and WSGI Middleware is Easy beschreibt wie man mit WSGI Middleware arbeitet und was das eigentlich ist.

Python Paste Power

Python Paste Power ist ein sehr interessanter Artikel über Python Paste, das Metaframework von Ian Bicking. Es macht die Anwendung und Verteilung von Web-Anwendungen in Python sehr viel einfacher (jedenfalls wenn das Framework mit dem man die Anwendung bauen will Paste Support hat).

IRC Logger update

The IRC logger is working fine, but I wasn't happy with the dependence on muh - so I wrote my own little logger bot in python, based on irclib. Does work fine and does only what I want it to do - logging. I allways feel a bit queasy when IRC bots have command structures and stuff like that and I actually don't need any of those ...

So now the project is mostly complete - just use the django admin to add channels to your database, point the logger bot to some IRC host and see how it joins channels and starts logging.

Oh, there are still things to do - for example the bot needs to rescan the list of channels so it notices newly added channels and leaves deleted channels (and maybe I should add channel activation/deactivation so I can switch off channels for some time without losing the archives), but for now it just logs #django and for that it's good enough.

IRC logger for #django

There now is a IRC logger for #django on freenode.net. Ever since the loglibrary broke down, I thought about rolling my own. So I started to build the stuff needed for an IRC logger. The interface itself is written with django, of course

As allways, the source is available in my trac instance. It's currently only running in #django, but it can easily be extended to other channels. And it stores log lines in a database, so I will be able to add searching and stuff like that. It already has a calendar view on the logs.

The logging itself is done with muh - a nice IRC proxy that allows logging to named pipes. Then there is a script fetch.py that pulls the lines from the named pipe and stores them in the database. The last part is the django-based viewer for those logs.

Update: the logging is now done with a dedicated IRC logger written in python. It's in the source tree as loggerbot.py.

I could make use of generic views in Django, only that I needed to parameterize them. I did that with a wrapper function that moves stuff from the keywords of a view function to the extra_lookup args and extra context keywords of generic views. You can see the code in the repository.

The rest is just standard Django stuff: generic templates (that make use of the cool regroup tag) and some custom tags for the calendar and the user colorization. A bit of model hackery and that's it, actually. Nice and simple. Took only a few hours to bang together, and that includes playing with muh and named pipes ...

TwistedDAV ist ein WebDAV Server in Python, der unter Twisted läuft. Sehr interessant wenn man etwas auf DAV aufbauen möchte - bisher gabs da nur den Zope-Source als Beispiel und Basis.

How-to get decimal.py if I have Python 2.3.x beschreibt, wie man Zahlen in Dezimaldarstellung (entgegen den dusseligen binären Floats mit ihren nervigen Rundungsproblemen) für Python 2.3 bekommt. Ab 2.4 ist das ja ein Standardmodul.

i18n and django

Jacob did set up branch commit rights for me and a branch for i18n stuff. So I worked today on the ideas in the patch on ticket 65 by nesh. I did write the stuff mostly from scratch because I wanted some things a bit differently and now it is available for testing.

So first on how you can use the i18n stuff with your django checkout. You need to have a current svn trunk checkout and go to the root of your checkout and issue the following command:

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

After that you should have a tree with my patches applied. I currently only translated very little stuff to make the patch and changes as small as possible, but I already added a german translation file with stuf for the admin index and the isAlphaNumeric validator. I think I will add some more stuff to the translations soon.

The patch only addresses the translation part - other things like date formatting, number formatting, timezone handling should go into different patches to make each one of them as small as possible. The translation object (that's the beast that is responsible for turning strings into their new form) is built on request. This gives us the chance to look at various places that might help in deciding what language to present to the user. The code starts by looking into the session for a django_language variable. If that isn't found, it looks into the cookies for a django_lanaguage cookie. If that isn't found, too, it digs at the HTTP headers. It looks for the Http-accept-language header and splits that up by languages and sorts them by preference. It will use the first language (ordered by preference, highest preferences first) found in the django messagefile repository. If none of those languages can be found, it will ultimately fall back to the default translation object that is defined by the LANGUAGE_CODE setting in your settings file.Message files can be stored in three different places: the django project message files are stored in the django.conf package in a locale subdirectory. This is much like the admin_media and admin template directories. The locale subdirectory is structured as is typical with locale storage: one subdirectory per language and a LC MESSAGES directory in there. The language domain for django message files is allways django . The next place where django looks for message files is in the project - if you have a locale directory in your project, you can store additional message files there. The third place is the application - you can have a locale directory besides your apps views directory. All locale directories are structured the same.

A translation object for a given language is actually a concatenation of four translation objects: first the application translation object. This will have a fallback to the project translation. That in turn will fallback to the global translation object which will fallback to the translation object for the default language. That way higher levels can override translations from lower levels and applications can provide their own translations.

The application for the translations is actually discovered by module introspection - it uses the view func to call on a URL to discover what application carries this view func and uses that to look for local translations. There are two tools provided to manage translations: make-messages.py and compile-messages.py. Both tools can be called in either the root of the django svn tree or in the project directory or the application directory. make-messages.py will scan the current directory and everything below that for strings to translate and will create a django.po file in the locale directory for the given language. compile-messages.py will just turn all .po files into .mo files. Adding translations is easy. In python code you just surround strings (only string constants!) with ('...') or ("..."). That will mark those strings for translation so that make-messages.py can pull them out and write them to the .po files. And it will translate the string on runtime, using the current translation object as discovered from the request. In templates there is the template tag {% i18n ('....') %} - same syntax as with python code, only you need to wrap it as a template tag. Those strings will be pulled from the .html files into the .po, too. The i18n tag supports string interpolation from the context: {% i18n ('blah %(blubb) s blubber') %} would first translate the string and then interpolate the context variable blubb into the translated result.

A hint: when writing strings to translate, don't use positional parameters for interpolation (the %s stuff) but use named parameters (%(blah) s) instead. That way people building translations can reorder the string without breaking your code - some languages have different orders from english.

Using the translations is easy, too: you just need to set your default language in the LANGUAGE_CODE setting and add the django.middleware.locale.LocaleMiddleware to your middleware setting. You need to put it to the top - especially in the admin it should come before the AdminUserRequired middleware - but it needs to be after the SessionMiddleware, if you use that.

That's it for a start, play with it and tell me when something goes wrong. Best place to tell is on the ticket in the 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.

Weil ichs für ein Firmenprojekt gesucht habe: InformixDB ist ein Python-DBAPI1-Client für die Informix Datenbanken. Funktioniert auch mit Informix SE.

DjangoScgi - Django Projects - Trac

Django with Apache and SCGI and Django with Apache and FCGI are two reworked documentations on how to get Django running with both FCGI and SCGI under Apache. I use the same parts as with my previous howtos, only that now SCGI is supported, too.

My gallery is currently running the Apache+SCGI setup, it's quite nice. The configuration in Apache is much nicer and cleaner than with the FCGI setup.

Since 2007 the links in this page didn't work any more, so I removed them.

Django Gallery Status

Again news from my gallery project - it's making good progress, even if some of my latest stuff isn't directly visible. I did a lot of reworking in the code and a lot of admin interface changes. Things in the source that might be interesting for other djangonistas:

  • I still have a fully filesystem based gallery - but I added a database stored cache. So now when contents is pulled from the filesystem, the code directly checks with it's database cache and updates that accordingly. This led to a heavy rework of the code so that now actually all stuff is driven by model objects - the cache entries are just part of the django model. Makes code much easier and allows the next two changes.
  • I added an automatic sitemap for galleries. First versions traversed the filesystem, but now it just uses the database cache to draw a folder hierarchy with thumbnails.
  • I added RSS support. The main gallery selection references a RSS feed over all galleries and the folder and image views within a gallery reference a RSS feed only for images within that gallery. This makes use of the RSS framework of Django.
  • the view that showed gallery folders and images was reworked to make much more use of django-like stuff: instead of lazy closures that are passed in to the template, I now use custom templates that help to reduce the code of the view drastically (ok, the code is moved to the taglib, but that's much better decoupling than before).

The result: the gallery is much faster, I have RSS up and running and I have automatic sitemaps. The code itself is much easier as it is more model-driven - the former clash between picturefolder model stuff on the one side and FSObject instances on the other side ist gone - and cache maintenance is automatic. And the XMLRPC integration is much faster, too. All in all some very useful internal changes.

Other changes are in the management interface where you now not only have the AJAX-stuff to change object names and hidden state, but can do that from a filemanager, too. That filemanager will be extended for simple management functions like moving, deleting etc., too. It already is able to create new folders.

Parser für Python-ähnliche Konfigfiles

Philip J. Eby hackt mal wieder. Diesmal einen Parser für Konfigurationssprachen deren Syntax an Python angelehnt ist. Besonders interessant: mit dem Parser kann man sehr schön abstrakte Sprachen aufbauen die in Teilen einfach Python für Code benutzen - man kann enthaltene Python-Blöcke nämlich von der Token-Form wieder in sauber formatierten und eingerückten Python-Code umwandeln. Der Parser kennt natürlich all die Randfälle der Sourceformatierung in Python und kommt mit denen auch klar.

Interessant ist das ganze deshalb, weil man mit Python ja leider keine Makrosprache hat und deshalb eigene Syntaxerweiterungen und Sprachen mit domain-spezifischer Syntax und Semantik nicht direkt in Python abbildbar sind - aber man kann über diesen Parser einen Translator für solche DSLs bauen und diese dann wieder in Python wandeln. Dazu dann noch ein bischen Import-Magic und man hätte sowas wie poor-mans-macros für Python ...

News from the Gallery project

News from my Django Gallery project: it's coming along nicely. If you want sample code for AJAX with Django or XML-RPC with Django - look into it's source. I implemented parts of the blogger API, metaWeblog API and MoveableType API - just enough so that Photon can post pictures to my gallery. AJAX is used for logged in users to change the title of pictures and folders by just clicking the title and for activating the toolbox on the backside of pictures. Additionally the gallery includes the usual PIL stuff like thumbnailing - I am especially proud of the flipped corner look for folder thumbnails Other stuff worth looking into might be the usage of lazy evaluation to push stuff into the template without precalculating it - it's only calculated when the template actually uses it. And interesting might be the abstraction of the filesystem based content - only the base path is stored in the database model, the rest of the gallery is in the filesystem (and can so be easily managed with FTP, SSH or directly on the unix shell).

Additionally you might look into it for handling of authentication - user registration isn't yet done, but will follow some day. As will comments and RSS - but I have to write that, first. And I am starting to write documentation - docstrings in the code and documentation pages in the wiki.

PostgresPy ist eine Sammlung verschiedener Python-Module rund um Postgres. Serverseite und Clientseite.

DjangoGallery - sample app with sample installation

Mal wieder englisch, da auch für #django interessant I have uploaded the source and made available the repository and a trac instance for my django projects. The first one that's there is the DjangoGallery - that's what I am using at viele-bunte-bilder.de. I have written a first take at an installation instruction that shows how to get the gallery running on your own site - you could even integrate it into your own project (although you will have to do some minor source changes - mostly replacing the projectname "gallery" by whatever your project is called).

The application isn't fully done, yet. There are many plans on how to extend it, as it will be my main motiviation to remove all other gallery software I am running (I already replaced PHP Gallery and now I am targeting my old mod_perl stuff and last one will be attacking my Wordpress based gallery), so be warned that it will change over time.

If you find bugs, feel free to file tickets. You can file enhancement wishes, too - but since the main target of this project will be to replace my own other gallery projects, it's doubtfull that I will do much work besides what is needed to reach that goal. At least for now - there's no limit to what can be done to the code after I phased out all PHP and Perl code

Since 2007 these links are non-functional, so I removed them.

Kibot ist ein in Python geschriebener IRC bot. Mit einfacher Pluginschnittstelle, so das man den gut als alround-bot auf einen Channel loslassen könnte.

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.

Lebende Daten

Komischer Titel, oder? Naja, mir ist einfach nur was aufgefallen bei der Beschäftigung mit Webframeworks und anderen Anwendungen, speziell im Ruby und Pyhton Umfeld. Und zwar die Art und Weise wie Minidaten gespeichert werden und wie zum Beispiel Konfigurationsdaten gehalten werden.

Im Java-Umfeld gibts da eine Inflation von XML-Mini-Languages - Berge von toten Daten. Tot deshalb, weil diese Daten eben nur im XML-Format leben und nur über XML-Werkzeuge bearbeitet und verändert werden können - habe ich zum Beispiel sich ständig wiederholende oder algorithmisch beschreibbare Konfigurationsblöcke (z.B. einen Berg von sich ziemlich ähnlich sehenden URL-Mustern für ein Webframework), kann ich diese nur über XML-Werkzeuge generieren - z.B. mittels XSLT aus einfacheren Formaten generieren. Oder ich schreib mir kleine Tools dafür.

In Ruby sieht die Situation ähnlich aus - nur das da statt XML dann eben YAML genommen wird. Letztendlich ist das aber auch nicht besser - die Konfiguration ist immer noch ein totes File.

Aber sowohl im Python-Umfeld als auch bei diversen anderen dynamischen Sprachen gibt es eine gute Alternative dazu: nimm einfach ein Modul in deiner Programmiersprache. Denn zum Beispiel Pythonmodule leben - ist die Struktur komplexer, aber teilweise repetitiv - einfach eine kleine Python-Funktion schreiben die bei der dynamischen Erstellung der Config hilft. Soll die Config teilweise aus Datenbankinhalten kommen - einfach eine Python-Funktion schreiben die diese Daten zur Laufzeit aus der DB liest und in die Config einmischt. Lebende Konfigurationdaten eben.

Natürlich kommen so Sicherheitsprobleme mit ins Spiel - wir wollen ja nicht den PHP-Fehler mit dem ewigen eval wiederholen. Was dazu also dringend notwendig wäre, wäre eine saubere Sandbox für solche Module. Leider ist genau da in Python ein massives Loch in der Implementierung. Es gab früher mal die Bytecodehacks, die auch wiederbelebt wurden - aber das sind eben nur Hacks. Auch die Methode mittels eingeschränkter Imports und Proxy-Objekten eine Scheinsandbox aufzubauen wie es Zope macht ist nicht der Weisheit letzter Schluss.

Perl bietet hier - wie bei allen Sicherheitsfeatures in Perl üblich wird das natürlich von fast keinem Projekt verwendet - eine sehr saubere Methode über die safe execution. Man kann bis ins kleinste hinein reglementieren was der Code in einer solchen Sandbox darf - und damit ist eine Konfiguration über Perl-Modul definitiv besser abzusichern als in Sprachen ohne so ein Konzept.

Java selber hat natürlich ein ziemlich ausgefeiltes Sicherheitsmanagement - zwangsweise, es soll ja auch in Browsern mit sehr stark beschränkten Rechten laufen. Dieses Security-Modell ist auch für Anwendungen nutzbar und könnte zum Beispiel für Servlets oder eben auch Java-Configs zum Einsatz kommen - vor allem da man mit Java ja auch problemlos Files zur Laufzeit übersetzen und dynamisch laden kann. Erklär mir jetzt mal einer warum die Java-Leute so fixiert auf XML sind, wo sie doch die besten Grundlagen für sichere lebende Daten haben ...

Das Safe-Modell von PHP ignorieren wir hier mal geflissentlich, denn das ist ein Sekt-oder-Selters-Modell - entweder läuft jeder Code unter safemode, oder garkeiner. Was wir bräuchten wäre aber eine selektive Aktivierung unterschiedlicher Sicherheitsklassen für einen einzelnen Codeblock oder Modulimport (ok, Modulimporte hat PHP auch nicht, nur Includes - ich sag ja, wir ignorieren es einfach mal).

Bisher kann man also bei Python nur dann mit lebenden Konfigurationen arbeiten, wenn man sich sicher ist das die Konfigurationen nur von Usern ohne böse Absichten bearbeitet werden. Django zum Beispiel benutzt nur lebende Konfigurationen - es wäre daher eine ziemlich dumme Idee, würde man z.B. die Konfigurationdateien bei zentral gehosteten Anwendungen über das Web editierbar machen.

Wir brauchen dringend eine saubere Sandbox für Python. Ich glaube sogar das wäre ein wichtigeres Teilprojekt als die diversen syntaktischen Erweiterungen die immer wieder angegangen werden.

kenosis ist eine Python Library für ein einfaches P2P-Protokoll das auf XMLRPC aufbaut. Simpel und schlicht, allerdings ohne jegliche Form von Verschlüsselung - sollte also über extern verschlüsselte oder anderweitig gesicherte Wege benutzt werden.

XchatPython ist ein Plugin für X-Chat mit dem man Erweiterungen in Python schreiben kann.

PyICU ist eine Integration der ICU C++ Schnittstelle in Python. Scheint recht vollständig zu sein, was den Umfang angeht. Integration mit Python Stringdatentypen ist ebenfalls gegeben.

Connecting databases to Python with SQLObject ist eine ganz nette Einführung in SQLObject - einen der netteren Object-Relation-Mapper für Python.

Unicode HOWTO für Python. Sollten Python-Programmierer lesen.

md5crypt.py ist der gleiche Algorithmus für MD5 Passwörter, nur diesmal in Python.

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.

Ian Bicking darüber was bei SQLObject derzeit passiert - es war ja recht still um einen der nettesten SQL-Object-Layer für Python geworden, aber jetzt gehts weiter. Für mich interessantester Punkt: Toolunterstützung für Datenbankupgrades. Ein Punkt der zum Beispiel auch bei Django noch fehlt.

Coroutinen für Python

Philip J. Eby hat einen Patch zur Implementation von PEP 342 geliefert. Damit stehen die Chancen das Python in Zukunft Coroutinen haben wird sehr gut.

Und das wiederum bedeutet das Python eine - wenn auch primitive - Form von Continuations bekommt. Jetzt fehlt nur noch das sowas wie statesaver mit in Python integriert wird - für Multishot Continuations (ok, erstmal nur copyable Coroutines, aber das wäre immerhin der Anfang).

Das alles natürlich nur um dann endlich in Webframeworks mit Continuations arbeiten zu können. Ok, geht mit CherryFlow jetzt schon, aber es wäre schon wenn das ganze in das Mainstream-Python wandern würde.

The Illusive setdefaultencoding

Ian Bicking hat in seinem Artikel über setdefaultencoding einen Verweis auf einen netten Trick drin: einfach mit reload(sys) das sys-Modul neu laden, damit setdefaultencoding verfügbar ist.

setdefaultencoding dient dazu einzustellen, welches Standardencoding bytestrings benutzen. Normalerweise ist das ASCII, kann aber auch auf iso-8859-1 oder utf-8 umgestellt werden - wenn man setdefaultencoding überhaupt hat. Das wird nämlich dummerweise beim Start der Python Laufzeitumgebung gelöscht - weil die Python-Macher da mal wieder die User bevormunden wollen.

reload(sys) ist natürlich etwas das nicht unbedingt für Vertrauen sorgt - sys ist ja ein nicht ganz unwichtiges Modul. Aber in meinen Experimenten hat es bisher gut geklappt und es hilft definitiv bei der ganzen Unicode-Problematik wenn man seinen programmen ein anderes Encoding als Standard verpassen kann.

Schöner wärs natürlich wenn in der Standarddistribution setdefaultencoding nicht mehr gelöscht würde. Klar, kann man auch erreichen durch einen Patch von site.py, aber das ist auch nicht besser als sys neu zu laden ...

Writing a Simple Filesystem Browser with Django

Dieser Artikel ist mal wieder in Englisch, da er auch für die Leute auf #django interessant sein könnte. This posting will show how to build a very simple filesystem browser with Django. This filesystem browser behaves mostly like a static webserver that allows directory traversal. The only speciality is that you can use the Django admin to define filesystems that are mounted into the namespace of the Django server. This is just to demonstrate how a Django application can make use of different data sources besides the database, it's not really meant to serve static content (although with added authentication it could come in quite handy for restricted static content!).

Even though the application makes very simple security checks on passed in filenames, you shouldn't run this on a public server - I didn't do any security tests and there might be buttloads of bad things in there that might expose your private data to the world. You have been warned. We start as usual by creating the filesystems application with the django-admin.py startapp filesystems command. Just do it like you did with your polls application in the first tutorial. Just as an orientation, this is how the myproject directory does look like on my development machine:


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

After creating the infrastructure, we start by building the model. The model for the filesystems is very simple - just a name for the filesystem and a path where the files are actually stored. So here it is, the model:


 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'], )


As you can see, the model and the admin is rather boring. What is interesting, though, are the additional methods isdir , files and file . isdir just checks wether a given path below the filesystem is a directory or not. files returns the files of the given path below the filesystems base path and file returns the real pathname and the mimetype of a given file below the filesystems base path. All three methods check for validity of the passed in path - if the resulting path isn't below the filesystems base path, a ValueError is thrown. This is to make sure that nobody uses .. in the path name to break out of the defined filesystem area. So the model includes special methods you can use to access the filesystems content itself, without caring for how to do that in your views. It's job of the model to know about such stuff.

The next part of your little filesystem browser will be the URL configuration. It's rather simple, it consists of the line in settings/urls/main.py and the myproject.apps.filesystems.urls.filesystems module. Fist the line in the main urls module:


 from django.conf.urls.defaults import *

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

Next the filesystems own urls module:


 from django.conf.urls.defaults import *

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

You can now add the application to the main settings file so you don't forget to do that later on. Just look for the INSTALLED_APPS setting and add the filebrowser:


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

One part is still missing: the views. This module defines the externally reachable methods we defined in the urlmapper. So we need two methods, index and directory . The second one actually doesn't work only with directories - if it get's passed a file, it just presents the contents of that file with the right mimetype. The view makes use of the methods defined in the model to access actual filesystem contents. Here is the source for the views module:


 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

See how the elements of the directory pattern are passed in as parameters to the directory method - the filesystem name is used to find the right filesystem and the path is used to access content below that filesystems base path. Mimetypes are discovered using the mimetypes module from the python distribution, btw.

The last part of our little tutorial are the templates. We need two templates - one for the index of the defined filesystems and one for the content of some path below some filesystem. We don't need a template for the files content - file content is delivered raw. So first the main index template:


{% if fslist %}
<h1>defined filesystems</h1> <ul> {% for fs in fslist %}
<li><a href="{{ fs.get_absolute_url }}">{{ fs.name }}</a></li> {% endfor %}
</ul> {% else %}
<p>Sorry, no filesystems have been defined.</p> {% endif %}

The other template is the directory template that shows contents of a path below the filesystems base path:


 {% if dlist or flist %}
 <h1>Files in //{{ fs.name }}/{{ path }}</h1> <ul> {% for d in dlist %}
 <li> <a href="{{ fs.getabsoluteurl }}{{ path }}{{ d }}/" >{{ d }}</a> </li> {% endfor %}
 {% for f in flist %}
 <li> <a href="{{ fs.getabsoluteurl }}{{ path }}{{ f.name }}" >{{ f.name }}</a> ({{ f.type }})</li> {% endfor %}
 </ul> {% endif %}

Both templates need to be stored somewhere in your TEMPLATE PATH. I have set up a path in the TEMPLATE PATH with the name of the application: filesystems . In there I stored the files as index.html and directory.html . Of course you normally would build a base template for the site and extend that in your normal templates. And you would add a 404.html to handle 404 errors. But that's left as an exercise to the reader.After you start up your development server for your admin (don't forget to set DJANGO SETTINGS MODULE accordingly!) you can add a filesystem to your database (you did do django-admin.py install filesystems sometime in between? No? Do it now, before you start your server). Now stop the admin server, change your DJANGO SETTINGS MODULE and start the main settings server. Now you can surf to http://localhost:8000/files/(at least if you did set up your URLs and server like I do) and browse the files in your filesystem. That's it. Wasn't very complicated, right? Django is really simple to use

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

Wer wie ich in die Situation kommt das er die Unicode-Strings in PySQLite2 nicht mag und utf8 Bytestrings braucht: PysqliteFactories sind hier die Lösung, nicht Konverter. Denn Konverter müssten auf jede Spielart von varchar registriert werden die in Benutzung ist - die Row Factories hingegen sind da ziemlich agnostisch und praktisch. Und wenn man schon eine eigene Cursor-Klasse benutzt: diese einfach als Cursor Factory auslegen die dann mit self.row_factory der Instanz eine Row Factory verpasst.

Django, lighttpd and FCGI, second take

In my first take at this stuff I gave a sample on how to run django projects behind lighttpd with simple FCGI scripts integrated with the server. I will elaborate a bit on this stuff, with a way to combine lighttpd and Django that gives much more flexibility in distributing Django applications over machines. This is especially important if you expect high loads on your servers. Of course you should make use of the Django caching middleware, but there are times when even that is not enough and the only solution is to throw more hardware at the problem.

Update: I maintain my descriptions now in my trac system. See the lighty+FCGI description for Django.

Caveat: since Django is very new software, I don't have production experiences with it. So this is more from a theoretical standpoint, incorporating knowledge I gained with running production systems for several larger portals. In the end it doesn't matter much what your software is - it only matters how you can distribute it over your server farm.

To follow this documentation, you will need the following packages and files installed on your system:

  • [Django][2] itself - currently fetched from SVN. Follow the setup instructions or use python setup.py install .
  • [Flup][3] - a package of different ways to run WSGI applications. I use the threaded WSGIServer in this documentation.
  • [lighttpd][4] itself of course. You need to compile at least the fastcgi, the rewrite and the accesslog module, usually they are compiled with the system.
  • [Eunuchs][5] - only needed if you are using Python 2.3, because Flup uses socketpair in the preforked servers and that is only available starting with Python 2.4
  • [django-fcgi.py][6] - my FCGI server script, might some day be part of the Django distribution, but for now just fetch it here. Put this script somewhere in your $PATH, for example /usr/local/bin and make it executable.
  • If the above doesn't work for any reason (maybe your system doesn't support socketpair and so can't use the preforked server), you can fetch [django-fcgi-threaded.py][7] - an alternative that uses the threading server with all it's problems. I use it for example on Mac OS X for development.

Before we start, let's talk a bit about server architecture, python and heavy load. The still preferred Installation of Django is behind Apache2 with mod python2. mod python2 is a quite powerfull extension to Apache that integrates a full Python interpreter (or even many interpreters with distinguished namespaces) into the Apache process. This allows Python to control many aspects of the server. But it has a drawback: if the only use is to pass on requests from users to the application, it's quite an overkill: every Apache process or thread will incorporate a full python interpreter with stack, heap and all loaded modules. Apache processes get a bit fat that way.

Another drawback: Apache is one of the most flexible servers out there, but it's a resource hog when compared to small servers like lighttpd. And - due to the architecture of Apache modules - mod_python will run the full application in the security context of the web server. Two things you don't often like with production environments.

So a natural approach is to use lighter HTTP servers and put your application behind those - using the HTTP server itself only for media serving, and using FastCGI to pass on requests from the user to your application. Sometimes you put that small HTTP server behind an Apache front that only uses mod proxy (either directly or via mod rewrite) to proxy requests to your applications webserver - and believe it or not, this is actually a lot faster than serving the application with Apache directly!

The second pitfall is Python itself. Python has a quite nice threading library. So it would be ideal to build your application as a threaded server - because threads use much less resources than processes. But this will bite you, because of one special feature of Python: the GIL. The dreaded global interpreter lock. This isn't an issue if your application is 100% Python - the GIL only kicks in when internal functions are used, or when C extensions are used. Too bad that allmost all DBAPI libraries use at least some database client code that makes use of a C extension - you start a SQL command and the threading will be disabled until the call returns. No multiple queries running ...

So the better option is to use some forking server, because that way the GIL won't kick in. This allows a forking server to make efficient use of multiple processors in your machine - and so be much faster in the long run, despite the overhead of processes vs. threads.

For this documentation I take a three-layer-approach for distributing the software: the front will be your trusted Apache, just proxying all stuff out to your project specific lighttpd. The lighttpd will have access to your projects document root and wil pass on special requests to your FCGI server. The FCGI server itself will be able to run on a different machine, if that's needed for load distribution. It will use a preforked server because of the threading problem in Python and will be able to make use of multiprocessor machines.

I won't talk much about the first layer, because you can easily set that up yourself. Just proxy stuff out to the machine where your lighttpd is running (in my case usually the Apache runs on different machines than the applications). Look it up in the mod_proxy documentation, usually it's just ProxyPass and ProxyPassReverse.

The second layer is more interesting. lighttpd is a bit weird in the configuration of FCGI stuff - you need FCGI scripts in the filesystem and need to hook those up to your FCGI server process. The FCGI scripts actually don't need to contain any content - they just need to be in the file system.

So we start with your Django project directory. Just put a directory public html in there. That's the place where you put your media files, for example the admin media directory. This directory will be the document root for your project server. Be sure only to put files in there that don't contain private data - private data like configs and modules better stay in places not accessible by the webserver. Next set up a lighttpd config file. You only will use the rewrite and the fastcgi modules. No need to keep an access log, that one will be written by your first layer, your apache server. In my case the project is in /home/gb/work/myproject - you will need to change that to your own situation. Store the following content as /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"
 )

I bind the lighttpd only to the localhost interface because in my test setting the lighttpd runs on the same host as the Apache server. In multi server settings you will bind to the public interface of your lighttpd servers, of course. The FCGI scripts communicate via sockets in this setting, because in this test setting I only use one server for everything. If your machines would be distributed, you would use the "host" and "port" settings instead of the "socket" setting to connect to FCGI servers on different machines. And you would add multiple entries for the "main" stuff, to distribute the load of the application over several machines. Look it up in the lighttpd documentation what options you will have.

I set up two FCGI servers for this - one for the admin settings and one for the main settings. All applications will be redirected through the main settings FCGI and all admin requests will be routed to the admin server. That's done with the two rewrite rules - you will need to add a rewrite rule for every application you are using.

Since lighttpd needs the FCGI scripts to exist to pass along the PATH_INFO to the FastCGI, you will need to touch the following files: /home/gb/work/myprojectg/public_html/admin.fcgi ``/home/gb/work/myprojectg/public_html/main.fcgi

They don't need to contain any code, they just need to be listed in the directory. Starting with lighttpd 1.3.16 (at the time of this writing only in svn) you will be able to run without the stub files for the .fcgi - you just add "check-local" => "disable" to the two FCGI settings. Then the local files are not needed. So if you want to extend this config file, you just have to keep some very basic rules in mind:

  • every settings file needs it's own .fcgi handler
  • every .fcgi needs to be touched in the filesystem - this might go away in a future version of lighttpd, but for now it is needed
  • load distribution is done on .fcgi level - add multiple servers or sockets to distribute the load over several FCGI servers
  • every application needs a rewrite rule that connects the application with the .fcgi handler

Now we have to start the FCGI servers. That's actually quite simple, just use the provided django-fcgi.py script as follows:


 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

Those two commands will start two FCGI server processes that use the given sockets to communicate. The admin server will only use two processes - this is because often the admin server isn't the server with the many hits, that's the main server. So the main server get's a higher-than-default setting for spare processes and maximum child processes. Of course this is just an example - tune it to your needs.

The last step is to start your lighttpd with your configuration file: lighttpd -f /home/gb/work/myproject/lighttpd.conf

That's it. If you now access either the lighttpd directly at http://localhost:8000/polls/ or through your front apache, you should see your application output. At least if everything went right and I didn't make too much errors.

Eunuchs liefert ein paar Funktionen nach die unter Python 2.3 noch nicht verfügbar sind. Speziell socketpair und recvmsg/sendmsg sind da sehr wichtig - für Serverprogrammierung mit preforked Servern zum Beispiel.

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.

Running Django with FCGI and lighttpd

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] itself - currently fetched from SVN. Follow the setup instructions or use python setup.py install .
  • [Flup][3] - a package of different ways to run WSGI applications. I use the threaded WSGIServer in this documentation.
  • [lighttpd][4] itself of course. You need to compile at least the fastcgi, the rewrite and the accesslog module, usually they are compiled with the system.

First after installing ligthttpd you need to create a lighttpd config file. The configfile given here is tailored after my own paths - you will need to change them to your own situation. This config file activates a server on port 8000 on localhost - just like the runserver command would do. But this server is a production quality server with multiple FCGI processes spawned and a very fast media delivery.


 # lighttpd configuration file
 #
 ############ Options you really have to take care of ####################

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

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

 these settings attch the server to the same ip and port as runserver would do

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

This config file will start only one FCGI handler for your admin stuff and the default number of handlers (each one multithreaded!) for your own site. You can finetune these settings with the usual ligthttpd FCGI settings, even make use of external FCGI spawning and offloading of FCGI processes to a distributed FCGI cluster! Admin media files need to go into your lighttpd document root.

The config works by translating all standard URLs to be handled by the FCGI script for each settings file - to add more applications to the system you would only duplicate the rewrite rule for the /polls/ line and change that to choices or whatever your module is named. The next step would be to create the .fcgi scripts. Here are the two I am using:


 #!/bin/sh
 # this is myproject.fcgi - put it into your docroot

export DJANGOSETTINGSMODULE=myprojects.settings.main

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


 #!/bin/sh
 # this is myproject-admin.fcgi - put it into your docroot

export DJANGOSETTINGSMODULE=myprojects.settings.admin

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

These two files only make use of a django-fcgi.py script. This is not part of the Django distribution (not yet - maybe they will incorporate it) and it's source is given here:


 #!/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()

As you can see it's rather simple. It uses the threaded WSGIServer from the fcgi-module, but you could as easily use the forked server - but as the lighttpd already does preforking, I think there isn't much use with forking at the FCGI level. This script should be somewhere in your path or just reference it with fully qualified path as I do. Now you have all parts togehter. I put my lighttpd config into /home/gb/etc/lighttpd.conf , the .fcgi scripts into /home/gb/public_html and the django-fcgi.py into /home/gb/bin . Then I can start the whole mess with /usr/local/sbin/lighttpd -f etc/lighttpd.conf . This starts the server, preforkes all FCGI handlers and detaches from the tty to become a proper daemon. The nice thing: this will not run under some special system account but under your normal user account, so your own file restrictions apply. lighttpd+FCGI is quite powerfull and should give you a very nice and very fast option for running Django applications. Problems:

  • under heavy load some FCGI processes segfault. I first suspected the fcgi library, but after a bit of fiddling (core debugging) I found out it's actually the psycopg on my system that segfaults. So you might have more luck (unless you run Debian Sarge, too)

  • Performance behind a front apache isn't what I would have expected. A lighttpd with front apache and 5 backend FCGI processes only achieves 36 requests per second on my machine while the django-admin.py runserver achieves 45 requests per second! (still faster than mod_python via apache2: only 27 requests per second) Updates:

  • the separation of the two FCGI scripts didn't work right. Now I don't match only on the .fcgi extension but on the script name, that way /admin/ really uses the myproject-admin.fcgi and /polls/ really uses the myproject.fcgi.

  • I have [another document online][6] that goes into more details with regard to load distribution

flup: random Python WSGI stuff - eine Sammlung von WSGI-Server-Adaptern für FCGI, SCGI und Apache Jakarta 1.3 Protokolle sowie noch ein paar WSGI-Middlewares zur Authentifizierung, Komprimierung und Fehlerhandling.

Leonardo ist ein CMS mit Blog und Wiki Modulen in Python. Im Moment noch recht schlicht als CGI, soll aber auf WSGI und Paste umgestellt werden und könnte dann ganz interessant als generelle CMS-Komponenten in einer WSGI-Lösung sein.

Python Paste ist ein Meta-Framework - ein Framework zur Erstellung neuer Web-Frameworks auf WSGI-Basis. Viele interessante Middleware-Module und eine Reimplementation von WebWare auf WSGI-Basis.

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

Jython 2.2 in der Mache

Die Jython Webseiten geben noch nix her, aber in der Mailingliste gabs vor ein paar Tagen eine Info das eine neue Alpha für Jython 2.2 raus ist - und zwar diesmal (war ja schon Ende 2004 mal so weit) eine die funktioniert. Viele Features der neueren Python-Versionen sind drin, auch Generatoren/Iteratoren. Von daher ist es nicht identisch mit Python 2.2, sondern eher ein gutes Stück auf den Weg zu Python 2.3 von den Features her. Da der Entwickler mit OS X arbeitet und dort entwickelt ist es relativ problemlos dort zu installieren.

Zur Installation, da das nirgendwo explizit erwähnt wird:


java -jar [jython .version.elend.langer.name.jar]

Dann kommt ein grafischer Installer der alles auf die Platte kippt. Dann in dem Zielverzeichnis noch zusätzlich folgende Befehle eingeben:


chmod 755 jython
chmod 755 jythonc

Dann sind die beiden (jython ist der Interpreter und jythonc ein Compiler) auch aufrufbar und es kann losgehen. Beim ersten Start von jython wird eine ganze Reihe von Systempaketen aktiviert, also nicht wundern über die vielen Meldungen vom sys-package-mgr.

Wer Jython nicht kennt: das ist eine Reimplementierung von Python auf der Java Virtual Machine. Dadurch lassen sich sehr elegant alle Java-Libraries benutzen und durch die interaktive Shell von Jython lässt sich auch mit Java-Klassen interaktiv spielen. Sehr schön um mal schnell Sachen auszuprobieren. Aber natürlich auch sehr schön um zwar die Portabilität von Java zu haben, aber nicht die kranke Sprache

Und es ist halt einfach witzig so Sachen wie die hier zu machen:


Jython 2.2a1 on java1.4.2_07 (JIT: null)
Type "copyright", "credits" or "license" for more information.
>>> import java.lang
>>> dir(java.lang.Number)
['byteValue', 'doubleValue', 'floatValue', 'intValue', 'longValue', 'shortValue']
>>> import java
>>> dir(java)
['__name__', 'applet', 'awt', 'beans', 'io', 'lang', 'math', 'net', 'nio', 'rmi', 'security', 'sql', 'text', 'util']
>>> ```

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.

Foundations of Python Network Programming ist ein relativ neues Buch über Netzwerkprogrammierung mit Python. Behandelt alle möglichen Ecken der Netzwerkprogrammierung die man sich denken kann - ziemlich klasse der erste Eindruck. Ich kenn zwar die meisten Sachen schon irgendwoher, aber so kompakt in einem Buch ist das trotzdem nett zum Nachlesen. Zusammen mit Dive Into Python würde ich die beiden als das ideale Gespann zum Python lernen sehen.

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

Kid ist eine recht interessante Python-Library die eine Template-Engine mit Fokus auf wellformed XML implementiert. Das Ergebnis ist ähnlich wie Zope Page Templates - also eine Attributsprache für XML mit Integration von Python. Und fix ist das ganze auch: ein XML-Template hat auf meiner Kiste um die 70 Hits/sec.