Kabelsalat

Aufgrund interner Restrukturierungsmaßnahmen (aka Möbelrücken) war ich gezwungen, mein jahrlang, mühsam aufgebautes Kommunikationsnetzwerk neu zu ordnen:

Vorher:

Nachher:

Übrig geblieben ist eine alte Fritzbox, die ich aber zur Sicherheit behalten werde, falls die jetzige mal Ihren Geist aufgibt. Daneben ein Fon-Wlan-Point den ich aufgrund der derzeitigen deutschen Rechtslage (Mitstöhrerhaftung, Vorratsdatenspeicherung) schon seit längerer Zeit nicht mehr in Betrieb hatte. Aber da hat sich wohl was geändert.

Und dann noch mein Draytek We2200, ein klasse Router mit Wlan, VPN und jedem Quatsch den sich ein Netzwerkadmin wünschen kann. Ich hatte gehofft, ihn zu einem fairen Preis noch bei eBay verhökern zu können. Dort wird er aber um die 4 Euro gehandelt, Neupreis damals 300,- DM. Smiley:  :-(

Ist ja auch klar, das WLan unterstützt nur WEP-Verschlüsselung und ist damit praktisch unbrauchbar. Leider ist es der beste WLan-Router den ich je hatte, durch seine 2 anständigen, großen Antennen erreicht es beste Ausleuchtung.

Dann bleibt er eben auch bei mir, gammelt erstmal im Keller. Wer weiß wofür ich ihn nochmal brauche. Smiley:  :-)

Achja, und ein Hoch auf farbige Netzwerkkabel. Die beste Erfindung seit der Spülmaschine.

Forms: Dynamisch Felder hinzufügen

Kurzer und schmerzloser Tipp für zwischendurch: Wie kann man in einem NewForms-Formular dynamisch Felder hinzufügen?

Abstraktion

Am einfachsten per Abstraktion. Das Formular mit den weiteren Feldern leitet sich einfach vom Basisformular ab:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from django import forms

class Userform(forms.Form):
    username = forms.CharField()
    passwort = forms.CharField(widget=forms.PasswordInput(render_value=False))

class UserformKontaktdaten(Userform):
    #                      ^ Abgeleitet von Userform
    vorname = forms.CharField()
    nachname = forms.CharField()

Ausgabe:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
>>> f = Userform()
>>> print f.as_ul()
<li><label for="id_username">Username:</label> <input type="text" name="username" id="id_username" /></li>
<li><label for="id_passwort">Passwort:</label> <input type="password" name="passwort" id="id_passwort" /></li>

>>> f = UserformKontaktdaten()
>>> print f.as_ul()
<li><label for="id_username">Username:</label> <input type="text" name="username" id="id_username" /></li>
<li><label for="id_passwort">Passwort:</label> <input type="password" name="passwort" id="id_passwort" /></li>
<li><label for="id_vorname">Vorname:</label> <input type="text" name="vorname" id="id_vorname" /></li>
<li><label for="id_nachname">Nachname:</label> <input type="text" name="nachname" id="id_nachname" /></li>

Laufzeitabhängig

Möchte man die Felder aber schon während der Formulargenerierung dynamisch hinzufügen, überschreibt man einfacherweise den Konstruktor und legt die fügt die neuen Felder zu den bestehenden hinzu:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Userform(forms.Form):
    username = forms.CharField()
    passwort = forms.CharField(widget=forms.PasswordInput(render_value=False))

    def __init__(self, kontaktdaten=False, *args, **kwargs):
        super(Userform, self).__init__(*args, **kwargs)

        # Hier dynamisch Felder hinzufuegen
        if kontaktdaten:
            self.fields['vorname'] = forms.CharField()
            self.fields['nachname'] = forms.CharField()

Ausgabe:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
>>> f = Userform()
>>> print f.as_ul()
<li><label for="id_username">Username:</label> <input type="text" name="username" id="id_username" /></li>
<li><label for="id_passwort">Passwort:</label> <input type="password" name="passwort" id="id_passwort" /></li>

>>> f = Userform(kontaktdaten=True)
>>> print f.as_ul()
<li><label for="id_username">Username:</label> <input type="text" name="username" id="id_username" /></li>
<li><label for="id_passwort">Passwort:</label> <input type="password" name="passwort" id="id_passwort" /></li>
<li><label for="id_vorname">Vorname:</label> <input type="text" name="vorname" id="id_vorname" /></li>
<li><label for="id_nachname">Nachname:</label> <input type="text" name="nachname" id="id_nachname" /></li>

Frettchen an die Macht?

Schockierend! Ist mir doch kürzlich aufgefallen, dass auf den neuen EU-Heimtierausweisen ein Frettchen neben einem Hund und einer Katze abgebildet ist. Sind Frettchen wirklichen schon so populär? Ich kenne niemanden, der ein Frettchen besitzt oder es auch nur plant. Strange...

Weiße Mäuse

Spass mit Newforms-Admin - Read-Only Felder

In Automatische Felder zeigte ich letztens, wie man Felder automatisch mit einem Wert versieht. Nun geht es wieder einen Schritt weiter: Wie kann man Felder als "Text" anzeigen, so dass keine Eingabe des Users möglich ist.

Einfach und schnell

Das Stichwort ist: Widgets. Widgets legen fest, wie die Daten später im HTML ausgegeben werden – und wie Widgets den Admin-Felder zugewiesen werden, habe ich in den letzten Artikeln schon zur Genüge erklärt.

HTML-Formular-Elemente besitzen schon seit gefühlten 200 Jahren ein Attribut, dass sie "nur lesbar" macht: disabled, oder XHTML-Konform: disabled="disabled". Attribute lassen sich blitzschnell den Feldern, besser gesagt den Widgets, zuweisen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from django.contrib import admin
from myproject.weblog.models import Entry

class EntryAdmin(admin.ModelAdmin):

    def formfield_for_dbfield(self, db_field, **kwargs):
        field = super(EntryAdmin, self).formfield_for_dbfield(db_field, **kwargs)

        # Das Titel-Feld read-only setzen
        if db_field.name == "title":
            field.widget.attrs = {'disabled': 'disabled'}
        return field

Read-Only Felder

Und fertig ist die Geschichte. Das ist mal Rapid Development. Leider – wie immer – nicht ganz, es gibt noch einen Stolperstein: Auch beim Anlegen eines neuen Eintrags wird das Feld ausgegraut, die disabled-Geschichte darf also nur Änderungen (change) eines Artikels betreffen.

Read-Only Felder

Der Methode formfield_for_dbfield fehlt ein "Status-Flag", ob wir uns im Add- oder Change-Modus befinden. Das ist aber schnell nachgeholt, die Methode change_view markiert zur Laufzeit das Admin-Objekt einfach als "_is_change" und während der Widget-Manipulation wird darauf geprüft.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class EntryAdmin(admin.ModelAdmin):

    def add_view(self, request, *args, **kwargs): 
        self._is_change_mode = False
        return super(EntryAdmin, self).add_view(request, *args, **kwargs)

    def change_view(self, request, object_id, *args, **kwargs): 
        self._is_change_mode = True
        return super(EntryAdmin, self).change_view(request, object_id, *args, **kwargs)

    def formfield_for_dbfield(self, db_field, **kwargs):
        field = super(EntryAdmin, self).formfield_for_dbfield(db_field, **kwargs)

        # Das Titel-Feld read-only setzen
        # aber nur wenn wir uns im change-Modus befinden
        if db_field.name == "title" and self._is_change_mode:
            field.widget.attrs = {'disabled': 'disabled'}
        return field

Jetzt aber richtig

Ok, Problem gelöst aber schön ist was anderes. Hier im Firefox wird der Text grau auf grau dargestellt. Die disabled-Sache kann also nicht der heilige Gral sein. Ein anderer Ansatz ist: Zeige den Text/Inhalt des Feldes an, ohne Formularelemente.

Auch hier ist es eine Widgetsache. Wir brauchen also ein Widget, dass statt eines HTMl-Formular-Elements einfach nur den Inhalt ausspuckt. Eigentlich ganz einfach:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from django.contrib import admin
from django import forms
from django.utils.safestring import mark_safe
from myproject.weblog.models import Entry

class ReadOnlyWidget(forms.HiddenInput):

    def __init__(self, append_text, *args, **kwargs):
        self.append_text = append_text
        super(ReadOnlyWidget, self).__init__()

    def render(self, *args, **kwargs):
        field_value = super(ReadOnlyWidget, self).render(*args, **kwargs)
        return mark_safe("%s <strong>%s</strong>" % (field_value, self.append_text))

class EntryAdmin(admin.ModelAdmin):

    def add_view(self, request, *args, **kwargs): 
        self._is_change_mode = False
        return super(EntryAdmin, self).add_view(request, *args, **kwargs)

    def change_view(self, request, object_id, *args, **kwargs): 
        self._is_change_mode = True
        self._obj = Entry.objects.get(pk=object_id)
        return super(EntryAdmin, self).change_view(request, object_id, *args, **kwargs)

    def formfield_for_dbfield(self, db_field, **kwargs):
        field = super(EntryAdmin, self).formfield_for_dbfield(db_field, **kwargs)

        # Dem Titel-Feld das read-only Widget zuweisen
        # aber nur wenn wir uns im change-Modus befinden
        if db_field.name == "title" and self._is_change_mode:
            field.widget = ReadOnlyWidget(append_text=self._obj.title)
        return field

Read-Only Felder

Was passiert da? Das Titel-Feld, ein Input-Textfeld, wird in ein HiddenInput umgewandelt, im Quellcode ist es also als <input type="hidden" .../> zu sehen. Der eigentlich sichtbare Text wird aus dem aktuellen Model-Objekt gezogen und einfach drangehängt. Wiedermal hat formfield_for_dbfield keinen Zugriff auf das aktuell zu ändernde Objekt, aber wie ihr seht, kann man es einfach in der change_view Methode auslesen und der Klasse global zuweisen.

Stolperstein ist hier das Hidden-Input-Feld. Ein User mit – ich nenne es mal: genügend krimineller Energie – kann mehr oder weniger einfach den Inhalt des Hidden-Feldes manipulieren. Vielleicht währe es sinnvoll, den Wert des Hidden-Feldes zu verschlüsseln oder mit einer Prüfsumme (Hash) zu markieren. Andererseits, ein User der es drauf anlegt, mit allen Mitteln so einen Titel zu ändern, sollte sowieso keinen Zugang zur Admin-Ebene bekommen. Smiley:  ;-)

Andere Wege

Bei dieser Sache beschleicht mich mehr und mehr das Gefühl, dass der Ansatz einfach zu kompliziert ist. Dieses "formfield_for_dbfield hat keinen direkten Zugriff auf request, model-objekt, change_status, also holen wir das alles in den globalen Scope" nervt.

Aber es gibt auch andere Ansätze, am besten gefällt mir derzeit die Methode auf djangosnippets.org. Und wer ein wenig googelt findet noch hundert weitere Möglichkeiten, Admin-Felder read-only zu setzen.

Seis drum, letztendlich geht es mir bei dieser Artikelserie auch nicht darum, den perfekten Lösungsvorschlag für Problem X zu zeigen, sondern viel mehr Mittel und Wege zu zeigen, wie man ein solches Problem lösen könnte. Am Ende kommen noch zehn weitere Problemchen dazu (Stichwort: Permission-Handling) und jeder muss für sich selbst wissen, was der beste Lösungsweg für sein Problem ist. Amen. Smiley:  :-)

Multiple Aktionen in Djangos Administration - Reloaded

Vor mehr als einem halben Jahr hatte ich einen Blogpost zum Thema Multiple Aktionen in Djangos Administration geschrieben. Nun ist schon einige Zeit ins Land gegangen, Django besitzt jetzt den Status Eins Punkt Null und das Admin wurde mit newforms-admin generalüberholt.

Das im Artikel angesprochene Prinzip funktioniert immernoch, ist aber nicht mehr zeitgemäß. Daher freut es mich, dass es nun ein eigenes App für dieses Thema gibt:

django-batchadmin von Brian Beck liefert alles, um multiple Deletes in Djangos Administrationsoberfläche auszuführen und erlaubt dazu noch, es mit eigenen Plugins zu erweitern. Das alles absolut konform mit newforms-admin. Sehr cool!

Getestet habe ich es leider noch nicht, der einzige Knackpunkt den ich auf Anhieb gefunden habe ist, dass django-batchadmin zwingend jQuery benötigt.

Zeig mal den Query

Oft will man schnell mal wissen, was Djangos DB-Api so im Hintergrund treibt, sprich: Welches SQL-Statement wird abgesetzt.

Es gibt viele Apps die das übernehmen, der letzte Schrei derzeit ist Rob Hudsons Django Debug Toolbar. Der Nachteil ist, dass die Ausgabe zumeist im Template stattfindet. Im Quellcode direkt gehts aber auch:

1
2
3
>>> q = Entry.objects.values('id')
>>> print q.query.as_sql()
('SELECT "weblog_entry"."id" FROM "weblog_entry" ORDER BY "weblog_entry"."published" DESC', ())

Die Ausgabe erfolgt dann im Terminal bzw. im Logfile des Webservers. Neat! Smiley:  :-)

Merkzettel für Git

Ein paar Stichpunkte zum Umgang mit git; in erster Linie als Gedankenstütze für mich:

  • Der Master Branch ist der stabile Zweig. Wenn du ein Feature hinzufügen willst, erstelle einen neuen Branch und merge ihn anschließend mit dem Master Branch.
  • Commite viel, commite oft. Commite jeden Pups.
  • git-rm löscht die Dateien wirklich endgültig von der Platte und entfernt sie nicht nur aus der Versionskontrolle. Smiley:  :-(
  • git-gui und gitk sehen schrecklich aus, sind aber mächtig und produktiv.
  • Giggle sieht gut aus, kommt aber nicht an gitk ran.
  • projekt/.gitignore ist unnötig, 90% der Einträge gehören in die ~/.gitignore
  • Subversion nervt immer mehr. git-svn erst recht.
  • Github ruled!
  • meld ist das beste diff-Tool des Planeten.

Zu guter letzt noch meine .gitconfig:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
[gui]
    recentrepo = /home/martin/Workspace/foobar
    fontdiff = -family \"bitstream vera sans mono\" -size 8 -weight normal -slant roman -underline 0 -overstrike 0
[user]
    email = martin@mahner.org
    name = Martin Mahner
[giggle]
    compact-mode = false
    main-window-maximized = false
    main-window-geometry = 1600x1123+0+52
[merge]
    tool = meld
[core]
    excludesfile = /home/martin/.gitignore
[color]
    branch = auto
    diff = auto
    interactive = auto
    status = auto

und meine .gitignore:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
*.pyc
*.pyo
*~
*.swp
*.orig
.project
.pydevproject
.DS_Store
MANIFEST
dist

Spass mit Newforms-Admin - Automatische Felder

Im letzten Artikel Rowlevel-Permissions ging es darum, wie man Einträge nur vom Benutzer selber bearbeiten lassen kann. Wer den Artikel verfolgt und aufgebaut hat, wird festgestellt haben, dass die ganze Sache einen gewaltigen Haken hatte:

In der Detailansicht konnte das Autorenfeld frei definiert werden, sprich Benutzer A konnte sich für Benutzer B ausgeben und umgekehrt. In diesem Artikel geht es darum, dem eingeloggten Autor automatisch den Artikel zuzuweisen.

Holzhammer

Die einfachste Lösung ist: Zeige das Autorenfeld gar nicht erst an und weise beim Speichern des Artikels dem Autorenfeld den eingeloggten User zu. Das Ganze ist schnell geschehen. Ähnlich wie die "newforms save()" Methode stellt auch ModelAdmin eine Methode zum Überschreiben zur Verfügung:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class EntryAdmin(admin.ModelAdmin):

    # Autorenfeld ausblenden (mit #7937 geht das irgendwann mal einfacher)
    # #7937: http://code.djangoproject.com/ticket/7973
    fields = (
        'title',
        'content',
    )

    # Formular valdidieren, Autor setzen und speichern
    def save_form(self, request, form, change):
        instance = form.save(commit=False)
        # Nur bei neuen Artikeln den User setzen
        if not change:
            instance.author = request.user
        return instance

Fertig ist die ganze Geschichte. Schnell, sicher und kaum zu überlisten.

Nicht ganz so krass

Vielleicht ist das aber auch schon einen Schritt zu weit. In meinem aktuellen Projekt möchte ich es trotzdem dem Benutzer überlassen, den Autor zu setzen, allerdings mit einer Kleinigkeit: Der aktuelle Benutzer soll in der Liste schon vorausgewählt sein.

Das Ganze ist ein wenig mehr "tricky". In ModelAdmin gibt es wiederrum eine Methode, die vor der Anzeige der Felder aufgerufen wird und mit der man ihr Verhalten und Aussehen (Widgets) beeinflussen kann: formfield_for_dbfield.

1
2
3
4
5
6
7
8
9
class EntryAdmin(admin.ModelAdmin):

    def formfield_for_dbfield(self, db_field, **kwargs):
        field = super(EntryAdmin, self).formfield_for_dbfield(db_field, **kwargs)

        # Autorenfeld hat als Vorauswahl den aktuellen User
        if db_field.name == "author":
            field.initial = request.user.pk
        return field

In dieser Methode prüfen wir, ob das aktuelle Feld unser "Autorenfeld" ist, dann wird ihm als Startwert (initial) der aktuelle User zugewiesen.

Nächster Haken: Der obige Code funktioniert nicht, in der Methode formfield_for_dbfield hat man keinen Zugriff auf das request-Objekt. Ein wenig Patching muss her:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class EntryAdmin(admin.ModelAdmin):

    def add_view(self, request,  *args, **kwargs):
        self._request = request
        return super(EntryAdmin, self).add_view(request,  *args, **kwargs)

    def change_view(self, request, *args, **kwargs):
        self._request = request
        return super(EntryAdmin, self).change_view(request,  *args, **kwargs)

    def formfield_for_dbfield(self, db_field, **kwargs):
        field = super(EntryAdmin, self).formfield_for_dbfield(db_field, **kwargs)

        # Autorenfeld hat als Vorauswahl den aktuellen User
        if db_field.name == "author":
            field.initial = self._request.user.pk
        return field

Glücklicherweise ist ModelAdmin flexibel genug, das Problem zu lösen. Die Methoden add_view und change_view werden vor der Anzeige des Formulares aufgerufen und beiden steht das Request-Objekt zur Verfügung. Das ist genau der richtige Ort, das Request-Objekt als globale Eigenschaft (self._request) der Klasse zuzuweisen.

Somit kann man in der formfield-Methode einfach auf das "globale" Request-Objekt zugreifen und mit den paar Handgriffen ist der Benutzer in der Autorenliste vorausgewählt.

Mit ein wenig Javascript kann man auch einfach aus diesen beiden Sachen eins machen:

Und nicht vergessen: Auch hier ist es wieder möglich mit Permissions zu arbeiten. So könnte ein Administrator immer den Autor setzen (zweite Variante), wo ein "normaler" User kein Autorenfeld zu Gesicht bekommt und automatisch gesetzt wird (erste Variante).

Spass mit Newforms-Admin - Rowlevel-Permissions

Newforms-Admin ist schon eine Weile verfügbar und daher wird es Zeit, sich einmal mit den Features auseinander zu setzen. Dieser Artikel ist der Anfang einer Reihe von Tipps und Tricks zu Newforms-Admin, die nacheinander alle aufeinander aufbauen.

Ein oft gewünschtes Feature im Admin-Interface ist "Lasse den Benutzer nur seine eigenen Einträge bearbeiten", anders ausgedrückt: Rowlevel-Permissions. Eine der einfachsten Aufgaben für das neue Admin-Interface.

Fangen wir wieder einmal mit dem Model an, ein kleines Weblog ist ein gutes Beispiel:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from django.db import models
from django.contrib.auth.models import User

class Entry(models.Model):
    title = models.CharField(max_length=255)
    content = models.TextField()
    author = models.ForeignKey(User)

    def __unicode__(self):
        return self.title

Um das Model mit dem Admin Interface administrieren zu können, ist eine einfache ModelAdmin-Klasse notwendig, bestens aufgehoben in der Datei admin.py (Jahaa):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from django.contrib import admin
from myproject.weblog.models import Entry

class EntryAdmin(admin.ModelAdmin):
    list_display = (
        'title',
        'author',
    )

admin.site.register(Entry, EntryAdmin)

Soweit nichts spektakuläres, mit ein paar Einträgen von verschiedenen Usern sieht die Übersichtsseite so aus:

Nun zur Aufgabe: Um dem Benutzer nur seine Beiträge bearbeiten zu lassen, lässt Newforms-Admin auf einfache Weise zu, das Queryset für die Listenansicht zu bearbeiten. Füge in deine ModelAdmin-Klasse eine Funktion "queryset" hinzu, die die Einträge anhand der User/Autor-Beziehung filtert:

1
2
3
4
class EntryAdmin(admin.ModelAdmin):
    # ...
    def queryset(self, request):
        return self.model._default_manager.filter(author=request.user)

Das war es auch schon! Jeder Autor sieht ab sofort nur noch seine eigenen Artikel:

Die Sache hat nur einen Haken: Auch Administratoren würden nur ihre eigenen Artikel sehen, irgendwie nicht sehr sinnvoll. Um das Problem zu lösen, schaffen wir uns am einfachsten eine zusätzliche Permission. Erweitere also dein Model wie folgt:

1
2
3
4
5
6
class Entry(models.Model):
    # ...
    class Meta:
        permissions = (
            ('can_view_all', 'Can view all Entries'),
        )

Nach einem "./manage.py syncdb" ist diese neue Permission auch schon verfügbar, die du deinem User zuweisen kannst:

Leider ist nicht genug Voodoo in Django vorhanden, als dass alles automatisch geht. In deiner Queryset-Redefinition musst du noch auf die neue Permission prüfen, etwa so:

1
2
3
4
5
def queryset(self, request):
    # ...
    if request.user.has_perm('weblog.can_view_all'):
        return self.model._default_manager.get_query_set()
    return self.model._default_manager.filter(author=request.user)

Und fertig. "Normale" User können nun nur noch ihre eigenen Beiträge bearbeiten während Administratoren oder User mit der Permission "can_view_all" alle Einträge angezeigt bekommen.

Zu guter letzt noch ein Hinweis: Theoretisch könnte man dieses Verhalten auch umdrehen und eine Permission "Can view only his own entries" (*urgs*) schaffen. Das nur fürs Gedächtnis.

Wiederverwendbare Django-Projekte

Eine der Stärken von Django ist die Wiederverwendbarkeit von einzelnen Applikationen (aka reusable apps). Will man aber ein komplettes Projekt veröffentlichen, sollte man sich auch Gedanken machen, wie man das komplette Projekt möglichst automatisch veröffentlichen kann. Anders ausgedrückt: der User soll möglichst wenig Pfade und Config-Einstellungen von Hand anpassen müssen.

Die verschiedensten Einstellungen eines Django-Projekts befinden sich in der Regel in der settings.py im Hauptverzeichnis deines Projekts, hier sind ein paar kleine Kniffe, wie du diese Einstellung möglichst dynamisch hälst:

Ein Hinweis zuvor: Die settings.py ist eine Python-Datei und wird auch von Python geparst. Es ist keine Ansammlung statischer Definitionen sondern in ihr kann auch eine komplette Programmlogik ausgelagert werden.

1. Dynamische Pfade

Die erste Notwendigkeit vor dem Deployment einer Seite ist es, die Pfade zu den Templates und den statischen Mediendateien anzupassen. Diese Arbeit können wir dem User abnehmen, indem wir sie von vornherein dynamisch festlegen. Grundlage allen ist es, erst einmal zu wissen, welcher Pfad zum Projektverzeichnis führt:

1
2
import os
PROJECT_ROOT = os.path.dirname(__file__)

Die Variable PROJECT_ROOT enthält damit den Pfad zum aktuellen Verzeichnis, sei es /var/www/meinprojekt/ oder unter Windows C:\Dokumente und Einstellungen\username\meinprojekt\.

Damit ist es möglich, die Pfade zu den Templates und den Mediendateien festzulegen. Eine Arbeit der der User vor dem Deployment also nicht mehr erledigen braucht:

1
2
3
4
5
MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'site_media')

TEMPLATE_DIRS = (
    os.path.join(PROJECT_ROOT, 'templates'),
)

2. Lass den SECRET_KEY geheim

Oft wird es vom User vergessen, den SECRET_KEY neu zu setzen. Er wird bspw. als Salt für die Passwort-Generierung benutzt. Am sichersten und einfachsten ist es für den User, den SECRET_KEY bei Bedarf sich selbst erstellen zu lassen: (Übernommen vom Byteflow-Projekt)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
try:
    SECRET_KEY
except NameError:
    SECRET_FILE = os.path.join(PROJECT_ROOT, 'secret.txt')
    try:
        SECRET_KEY = open(SECRET_FILE).read().strip()
    except IOError:
        try:
            from random import choice
            SECRET_KEY = ''.join([choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)])
            secret = file(SECRET_FILE, 'w')
            secret.write(SECRET_KEY)
            secret.close()
        except IOError:
            Exception('Please create a %s file with random characters to generate your secret key!' % SECRET_FILE)

Der kleine Schnipsel prüft, ob eine Variable SECRET_KEY schon definiert wurde. Wenn nicht, erstellt er eine Datei secret.txt im Projektverzeichnis und schreibt darin selbstständig einen zufälligen String. (Der Webserver muss in diesem Fall Schreibrechte für das Projektverzeichniss besitzen.)

3. Don't touch the settings

In der Bildbearbeitung gibt es einen Leitsatz: Don't touch the Pixels. Das bedeutet, wenn man ein Bild bearbeiten will, legt man sich zuvor eine Kopie der Bildebene an oder erstellt einen Snapshot, um im Bedarfsfall auf die Originaldaten zurück greifen zu können.

Mit den Django-Settings verhält es sich ähnlich. Ich füge in alle meine Settings-Dateien am Ende diesen Schnipsel ein:

1
2
3
4
try:
    from local_settings import *
except ImportError:
    pass

Er versucht, eine Datei "local_settings.py" zu finden und übernimmt/überschreibt die Einstellungen aus dieser Datei in die globalen Einstellungen. Diese "local_settings.py" ist der perfekte Ort für die Einstellungen, die vom User in jedem Fall gesetzt werden müssen – die Einstellungen für den Datenbankzugriff etwa.

Der große Vorteil kommt aber erst zum Tragen, wenn man das Projekt regelmäßig aus einem VCS aktualisiert. Da im Idealfall keine Daten in der ursprünglichen "settings.py" geändert werden, gibt es keine Kollisionen mit Updates. Der User kann also das Projekt regelmässig aus dem VCS aktualisieren, ohne jedes Mal die Einstellungen neu schreiben zu müssen.

Das waren drei kleine Kniffe, die das Deployment eines Projekts für den User sehr erleichtern. Das Ganze lässt sich noch beliebig fortführen, ist aber in den meisten Fällen projekt-abhängig. Hab ich noch etwas wichtiges vergessen, dann schreibt doch einen Hinweis in die Kommentare. Smiley:  :-)

Seite: < Zurück12345Weiter >Letzte »