Externer Server für statische Dateien einer Django-App


Eigentlich ist das Auslagern von statischen Files mit Django ja relativ simpel, aber schwierig wird es, wenn vom Benutzer Dateien hochgeladen werden können. Diese müssen auf dem static-Server zeitgleich verfügbar sein.

Entweder es kümmert sich gleich Django darum, die Datei weiterzureichen, was aber zusätzlichen Code erfordert. Oder es wird ein anderer Weg gewählt: über sshfs. Die Django-Installation behandelt alles so, als wäre es lokal. Das media-Verzeichnis allerdings liegt auf dem static-Server und wird per sshfs eingehängt.

Dann muss nichts "per Hand" synchronisiert werden und die Übertragung läuft verschlüsselt zwischen den Servern.

Damit das klappt, sind folgende Schritte notwendig:

  • Auf dem Django-Server muss das Paket sshfs installiert werden.
  • Da gleich beim Booten des Servers das Einhängen erfolgen soll, muss ein SSH-Login ohne Passwort vom Django-Server auf den static-Server möglich sein. Dazu macht man folgendes
    • Auf dem Django-Server als root anmelden und ein Keypair erzeugen mit ssh-keygen -t rsa. Dabei keine Passphrase vergeben. Der Schlüssel liegt dann unter /root/.ssh/id_dsa
    • Auf dem static-Server einen Nutzer erzeugen, der sich per SSH einloggen kann
    • Den Inhalt der Datei /root/.ssh/id_dsa.pub vom Django-Server in die Datei /home/NUTZER/.ssh/authorized_keys anhängen. Notfalls das Verzeichnis /home/NUTZER/.ssh erzeugen (und Rechte setzen).
    • Testen, ob der Login klappt vom Django-Server aus. Ein einfaches ssh NUTZER@static-server sollte klappen
    • Wenn gewünscht, kann auf dem static-Server der Login von NUTZER noch beschränkt werden durch AllowUsers NUTZER@IP in der Datei /etc/ssh/sshd_config. (Dabei aber natürlich andere Nutzer nicht vergessen, sonst sperrt man sich selbst aus!)
  • Nun auf dem static-Server alles vorbereiten:
    • Verzeichnis erzeugen, indem die Dateien liegen werden, bsp. /var/www/django1-static
    • Dieses Verzeichnis muss dem Nutzer NUTZER aus vorherigem Schritt gehören
  • Auf dem Django-Server die notwendigen Schritte durchführen:
    • Editieren der Datei /etc/fstab und einfügen folgender Zeile:

      sshfs#NUTZER@static-server:/var/www/django1-static /opt/django-app/media fuse uid=33,gid=33,umask=0,allow_other,port=222,noauto 0 0
      

      Damit wird das Verzeichnis /var/www/django1-static auf dem static-server ins media-Verzeichnis der Django-App unter /opt/django-app/ gemountet. Wichtig sind die Parameter:
      • uid und gid sollten die ID des Nutzers haben, unter dem die Django-App läuft (33 = www-data, kann in der Datei /etc/passwd herausgefunden werden).
      • Wenn SSH auf einem anderen Port läuft, dann ist die Port-Angabe wichtig.
    • Der Nutzer, der mit uid angegeben ist, muss in der Gruppe fuse sein: sudo adduser www-data fuse
    • Damit die SSH-Verbindung auch offen bleibt, noch folgende Zeile ans Ende der Datei /etc/ssh/ssh_config anhängen:

      ServerAliveInterval 15
      

      Das sendet alle 15 Sekunden ein Paket, um die Verbindung aktiv zu halten.
    • Mit mount -a testen. Es sollten unter /opt/django-app/media alle Dateien sichtbar sein, die auf dem static-server liegen.
    • Unmounten geht übrigens mit sudo fusermount -u /opt/django/media

Das wars. Die Django-App müsste nun alle Dateien ganz normal im media-Verzeichnis unter /opt/django-app/media speichern können. Diese Dateien werden sofort auf den static-server übertragen und dort gespeichert - völlig transparent für die Django-App.

Remount, wenn die Verbindung weg ist

Eine große Schwachstelle hat dieses System aber: Wenn der static-Server neu gestartet wird oder ein Netzwerkproblem hat oder ähnliches, ist der Mount weg und das Verzeichnis auf dem Django-Server leer. Somit klappen Uploads der Django-App nicht mehr und es kommt zu Fehlern. Eine Möglichkeit, dies etwas abzuschwächen, ist, ein Skript laufen zu lassen (z.B. jede Minute per Cron), das prüft, ob ein Mount vorliegt und ggf. neu mountet:

#!/bin/bash

if ! mount | grep "on /opt/django-app/media type fuse.sshfs" > /dev/null; then
    echo "SSHFS-Mount /opt/django-app/media ist weg. Versuche neu zu mounten..."
    mount /opt/django-app/media
fi

Somit fällt die Verbindung für max. 1 Minute aus, zumindest wenn der static-Server nur ein kurzes, temporäres Problem hat. Lässt man sich mit MAILTO in der crontab per Mail benachrichtigen, weiß man zumindest, das ein Problem vorliegt.

Nachtrag (26.05.): Dieses Skript ist nun nicht mehr optional, sondern elementar wichtig. Siehe http://www.bheil.net/blog/2010/05/26/sshfs-fstab-mounten/!

admin-media

Für die Django-admin-Files ist es die beste Lösung, diese weiterhin vom Django-Server ausliefern zu lassen. Dann müssen die nicht gespiegelt werden, vor allem wenn mit trunk gearbeitet wird. Also einfach ein virtual_host, der dieses Verzeichnis ausliefert und das dann in der settings.py angeben.

Python 2.6 DeprecationWarning


Soeben habe ich den Server auf Ubuntu 9.04 aktualisiert. Das lief astrein und alles funktioniert einwandfrei. Nur selbst installierte Python-Module musste ich "neu einbinden" (sprich: Symlinks neu setzen und fertig).

Aber da nun Python 2.6 die Standard-Python-Version ist und bei mir etliche Python-Skripte - selbst geschriebene und fremde - in Cron Jobs laufen, quillt mein Posteingang wegen Mails aus eben diesen Cron Jobs über. Und alle handeln von DeprecationWarnings, also Warnungen, das Modul xy mit Python 3.0 wegfallen wird. Nun ist dies natürlich ungemein praktisch - für den eigenen Code. Aber keine dieser Warnungen kommt von meinem eigenen Code, sondern von fremden. Grmpf.

Bisher seh ich noch keine Möglichkeit, das sauber zu lösen, bis die entsprechenden Programmierer das angepasst haben und die neuen Versionen über die gängigen Pakete verfügbar werden. Bis dahin bleiben zwei "unsaubere Lösungen":

- Wird das Skript direkt über python aufgerufen, reicht es, den Parameter -Wignore anzugeben. Das ignoriert aber alle Warnungen!

- Bei schweren Fällen geht nur ein gepflegter Eingriff in das Python-Modul selbst. Da etliche Python-Skripte Warnungen wegen dem sets-Modul ausgeben, kann diese durch ein Editieren der Datei /usr/lib/python2.6/sets.py und dem Auskommentieren des Codes unterdrückt werden:

#import warnings
#warnings.warn("the sets module is deprecated", DeprecationWarning,
#                stacklevel=2)

Ist aber zugegeben keine gute Lösung. Aber bis das "Problem" in allen Skripten gelöst sein wird, wird es noch dauern. Bisher scheint das auch nur beim sets-Modul notwendig zu sein.

Kennt jemand eine bessere Lösung? Bin für jede Hilfe dankbar.

PISA: PDF-Dokumente in Python einfach erstellt


Auf meiner Arbeit wurde sehr lange ein Word-Makro verwendet, um Text in eine bestimmte Form zu pressen und dann auszudrucken. Dieses Makro war nicht gerade einfach in der Bedienung und relativ fehleranfällig, überdauerte aber die Zeit von Office 97, Office XP bis hin zu Office 2003 - in Office 2007 funktioniert es schlicht nicht mehr.

Nun stand ich vor dem Problem, das Makro in Office 2007 wieder zu flicken (oh Graus), oder vielleicht gleich das Ganze in einer anderen Umgebung umzusetzen. Die Wahl war klar und die betroffenen Kollegen, die damit am Ende arbeiten würden, hatten nichts gegen meinen Vorschlag, eine kleine Webanwendung zu schreiben, die die Konvertierung macht und daraus ein PDF generiert.

Die Django-Applikation war schnell geschrieben, aber da ich noch nie in Python ein PDF erstellt hatte, dachte ich, dass wird ein ziemlich großer Aufwand - bis ich auf Pisa stieß. Das ist nicht der berühmt-berüchtigte Test für das Bildungssystem, sondern ist ein HTML/CSS-zu-PDF-Konverter für Python. Lizenziert unter der GPLv2 steht dem Einsatz nichts im Wege.

Wer also HTML/CSS kann, der kann damit auch ziemlich einfach PDF-Dateien erstellen. Ein Bild könnt Ihr Euch auf der Demo-Seite von Pisa machen. Die Handhabung ist simpel und in der Dokumentation steht alles, was man wissen muss.

Damit war das angesprochene Projekt in wenigen Stunden umgesetzt. Und kein Microsoft Office ist mehr nötig - nur ein Browser und ein PDF-Reader. Zu guter Letzt ist auch der Arbeitsplatz nicht mehr relevant. Die Kollegin kann den Druck anstoßen, wo auch immer sie gerade arbeitet.

Open Source macht einfach produktiver! Smile

Subversion beibringen, *.pyc-Files zu ignorieren


Wer Python-Projekte mit Subversion verwaltet, wird das leidige Thema kennen, das Subversion auch kompilierte Python-Dateien mit der Endung pyc beachtet. Mit folgendem Befehl kann das unterbunden werden: Die Endung *.pyc wird in jedem Verzeichnis zur Eigenschaft svn:ignore hinzugefügt und somit beachtet Subversion das nicht mehr.

svn -R propset svn:ignore "*.pyc" .

Irgendwo stand, dass Subversion in der neuesten Version pyc-Files nicht mehr beachtet würde. Damit wäre das hinfällig. Aber in SVN 1.5.1 ist das anscheinend noch nicht der Fall.

Python Cheat Sheet


Eine sehr schöne Sache ist dieses Cheat Sheet für Python-Entwickler. Das Wichtigste (bzw. oft verwendeste) schnell im Blick.

Checkout des Django 1.0 Subversion-Branches


Da das leider nicht direkt auf der Django-Webseite verlinkt ist und ich immer danach suchen muss, hier die Anweisung, wie die derzeit stabile Version 1.0 von Django direkt aus dem SVN ausgecheckt werden kann:

svn co http://code.djangoproject.com/svn/django/branches/releases/1.0.X/ django-1.0.x

In diesen Branch fliessen nur Sicherheitsaktualisierungen. Somit bietet sich dies für den produktiven Einsatz an.

Django 1.0 Released!


Wenn das keine freudige Nachricht ist: Django 1.0 ist endlich fertig! Yippie! Smile

Eines der leistungsfähigesten Web-Frameworks hat es zum ersten richtig großen Milestone geschafft. Ein dickes Danke an die Entwickler, die eine fantastische Arbeit leisten.

Download Release Note

Empfehlenswerte Eclipse-Plugins


Wenn man Django-Projekte mit Eclipse bearbeitet, lohnt es sich, folgende Plugins zu installieren (neben PyDev logischerweise):

- GEF (wird vom HTML-Editor benötigt) wie in der GEF-FAQ beschrieben

- Amateras, ein HTML-Editor, damit man die Templates auch schön bearbeiten kann

- Wer mit SVN arbeitet, sollte noch Subclipse installieren.

Kennt noch jemand ein paar hilfreiche Plugins?

Weiter empfehlenswert ist, ein eigenes Projekt in Eclipse zu erstellen, dass den Django-Source enthält. Dieses kann man dann in seinen eigenen Projekten als Abhängigkeit definieren. Klickt man nun auf einen Bezeichner, sprint Eclipse sofort zur Deklaration direkt im Django-Code!

Django-Projekte in Eclipse debuggen


Um mit Eclipse ein Django-Projekt zu debuggen, sind folgende Schritte notwendig (dies bezieht sich auf die Eclipse-Version 3.2.2 aus den Ubuntu-Quellen, andere Versionen sind nicht getestet):

- Eclipse und PyDev installieren, falls noch nicht geschehen. Unter Ubuntu dazu die beiden Pakete "eclipse" und "eclipse-pydev" installieren, die die entsprechenden Abhängigkeiten auflösen.

- Eclipse starten und unter Window->Preferences->PyDev->Interpreter Python als Interpreter python suchen und auswählen - das ist in der Regel /usr/bin/python.
Unter PYTHONPATH den direkten Pfad zur Django-Installation eintragen (bei mir z.B. /opt/django/trunk/django).

Um Django-Projekte zu integrieren, muss man folgendes tun. Dabei sollte das Django-Projekt ganz normal über die manage.py erstellt worden sein, wie man es sonst auch macht. Ist das geschehen, setzt man da ein Eclipse-Projekt drauf:
- Ein neues Projekt in Eclipse erstellen über File->New project. Dort ein pydev-Projekt auswählen. Unter "project contents" das Häkchen entfernen und direkt den Pfad zum schon erstellten Django-Projekt wählen. Die manage.py z.B. sollte direkt in diesem Verzeichnis liegen.
"Create default 'src' folder and add it to the pythonpath?" kann dabei angehakt bleiben.

- Um das Debuggen zu ermöglichen, muss eine neue Debug-Konfiguration erstellt werden. Über Run->Debug... kann durch einen Doppelklick auf "Python Run" eine neue Konfiguration erstellt werden. Dort wird als Projekt das eben erstelle Projekt gewählt und als "Main Module" die Datei "manage.py" ausgewählt. Im Reiter "Arguments" trägt man unter "program arguments" noch "runserver --noreload" ein und klickt auf "Debug". Dann sollten Breakpoints funktionieren.

Django's Admin-App verwendet newforms - und zwingt zu Code-Anpassungen


Im Zuge des Sprints auf Django 1.0 wurde nun im trunk die Admin-App so aktualisiert, dass newforms verwendet werden. Zwar ergeben sich dadurch wirklich tolle Möglichkeiten, allerdings sind diese Änderungen leider nicht rückwärtskompatibel und man muss Dinge im eigenen Code zwangsläufig anpassen.
Aber aufgrund der tollen Dokumentation von Django ist das kein sonderlich großes Unterfangen, wie ich freudig festgestellt habe. Smile

Wer sich den neuesten trunk aus dem SVN auscheckt und dann den Entwicklungsserver startet, wird Fehlermeldungen wie z.B. diese erhalten:

TypeError: __init__() got an unexpected keyword argument 'prepopulate_from'

Das liegt daran, dass nun Model-Definitionen von Angaben für die Admin-App bereinigt wurde. Diese Angaben wandern nun in eine extra Klasse, die dann in der Admin-App registriert wird. Dies bringt einige Vorteile: Man kann munter neue Klassen von früheren ableiten und sich sogar mehrere Admin-Oberflächen basteln, die unter verschiedenen URLs erreichbar sind (zum Beispiel darf dann Klaus nur den Admin-Bereich Bäckerei betreten und neues Gebäck zur Datenbank hinzufügen, während Egon nur die Getränkdatenbank bearbeitet und neue Bier-Sorten eintragen kann). Außerdem macht das Model-Definitionen übersichtlicher und sorgt für eine logische Trennung.
Eine schöne Übersicht, was man damit alles anstellen kann, gibts im Django-Wiki.

Es gibt zu der Sache auch einen sehr guten Screencast, den man sich erstmal anschauen sollte in folgendem Artikel:

http://oebfare.com/blog/2008/jul/20/newforms-admin-migration-and-screencast/

Die Änderungen im Detail und was zu tun ist, kann man auch nochmal auf einer Wiki-Seite nachlesen.

Neugestaltung der Webseite oder mein Weg zu Django ...


Endlich ist sie fertig, zumindest in der ersten Beta-Version: meine neue Webseite, komplett in Django geschrieben, konform zu XHTML 1.0 Strict und mit validem CSS.

Ich hoffe, den werten Leser und Besucher spricht die neue Seite an. Am Design habe ich mich ein wenig an der Einfachheit von Google orientiert. Trotzdem sollte es ein relativ zeitloses Design sein, womit man auch noch in einigen Monaten oder Jahren zufrieden sein kann, und dessen Farben ein gutes Gefühl bei gleichzeitig guter Lesbarkeit gewährleisten. Und dabei sollten natürlich die Standards eingehalten werden, so dass die Seite überall gut lesbar ist, egal welcher Browser oder OS (iPhone konnt ich leider nicht probieren Wink ) und auch ohne CSS sollte sie gut strukturiert bleiben. Ich denke, dass ist mir soweit gelungen (jedenfalls in meinen Tests Two thumbs ). Yippie!

Aber warum denn überhaupt eine neue Webseite? War das alte Wordpress-Blog nicht gut? Huh