Wiederverwendbare Django-Projekte

Geschrieben 3 Tage, 14 Stunden zuvor

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:

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:

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)

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:

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

Kommentare lesen und schreiben…




Firefox cached keine Dateien via SSL

Geschrieben 1 Woche, 1 Tag zuvor

Das Trac-System auf django-de.org erschien mir immer sehr langsam. 5 Sekunden für eine Seite war die Regel. Trotz dem die Response-Headers brav einen ETag aufwiesen macht Firefox 3 einfach kein Conditional Get, sprich jede Datei (Bilder, Stylesheets etc.), egal wie alt sie sind, werden bei jedem Request neu geladen.

Der Grund war dann doch schnell gefunden: Das soll so! Firefox speichert keine Dateien die über https kommen auf der Platte. In gewisser Hinsicht ein Sicherheitsgewinn, für mich in erster Linie aber nervig, da ich Ticketsysteme recht häufig nutze, und diese zumeist über SSL/TLS gehandelt werden.

Die Lösung des Problems ist auch schnell gefunden: In den globalen Einstellungen about:config setzt man einfach browser.cache.disk_cache_ssl von false auf true und schon cached Firefox auch Dateien von SSL-Quellen auf der Platte.

Ob das ein Sicherheitsrisiko darstellt, muss jeder für sich selbst entscheiden.

Bugreport und Diskussion auf bugzilla.mozilla.com.

Kommentare lesen und schreiben…




Generische Dateinamen für generische Apps

Geschrieben 2 Wochen, 1 Tag zuvor

In Django gibt es nur wenige Konventionen, wie man seine Dateinamen (bzw. Python-Module) benennen soll. Wächst das Django-Projekt wird man nicht drumrum kommen, Code in eigene Module auszulagern. So hat es sich z.B. eingebürgert, forms-Formulare in eine Datei "forms.py" der jeweiligen Applikation legen.

In #django-de gab es in letzter Zeit häufig Fragen und Diskussionen zu diesem Thema und nun hat Arne Brodowski sich die Mühe gemacht, diese Dateinamen einmal aufzulisten und zu beschreiben:

http://www.django-hosting.de/wiki/BestPracticesDateinamen/

Um es noch einmal klar zu stellen: Niemand wird gezwungen, diese Dateikonventionen zu verfolgen. Wer aber sein Projekt oder Applikation veröffentlichen will, sie möglichst wiederverwendbar gestalten will, tut gut daran, sich an diese Quasi-Standards zu halten. Diese kommen auch nicht von irgendwo sondern sind aus Erfahrung vieler Open-Source Projekte entstanden.

Danke Arne für die Liste, vielleicht währe es eine Gute Idee, wenn diese Informationen auch im offiziellen Django-Wiki Einzug halten würden.

Kommentare lesen und schreiben…




Zeile verdoppeln in Eclipse

Geschrieben 3 Wochen, 6 Tage zuvor

Im Editor Scite gibt es ein wunderbar kleines Feature: Mit der Tastenkombi CTRL+D verdoppelt man die aktuelle Zeile oder Auswahl. Diese Funktion braucht man vielleicht nicht so oft in der Programmierung, ich vermisse sie aber insbesondere im Umgang mit Javascript und HTML. Leider ist in den meisten anderen IDEs diese Tastenkombination mit "Zeile löschen" voreingestellt was einfach nur nervt.

Wolfram Kriesing hat nun eine Möglichkeit gezeigt, wie man Komodo dieses Verhalten beibringt und gleichzeitig habe ich noch einmal geschaut, ob Eclipse/Aptana das nicht auch irgendwie unterstützt.

Und siehe da, es geht sogar von Haus aus. CTRL+ALT+UP ist die Standardkombi dafür, die sich natürlich einfach neu mappen lässt. Smiley:  :-)

Kommentare lesen und schreiben…




Newforms AdminOptions besser strukturieren

Geschrieben 4 Wochen zuvor

Wer in den letzten Tagen seine schon etwas ältere Django-trunk Version aktualisiert hat, dürfte sein blaues Wunder erlebt haben, positiv oder negativ. Unter anderem hat Newforms Admin ja endlich Einzug gehalten.

Anders als bisher werden Einstellungen der Models nun nicht mehr in einer Subklasse Admin des Models definiert, sondern in einer eigenen:

class Entry(models.Model):
    title = models.CharField(max_length=255)
    slug = models.SlugField(blank=True, db_index=True)
    content = models.TextField()
    published = models.DateTimeField(auto_now_add=True)

class EntryAdmin(admin.ModelAdmin):
    save_on_top = True
    list_select_related = True
    date_hierarchy = 'published'
    list_display_links = ('title')
    search_fields = ('title', 'content')

admin.site.register(Entry, EntryAdmin)

Nun gibt es prinzipiell zwei Möglichkeiten, diese Admin-Optionen an die Models zu "binden":

  • autodiscover() durchsucht die Apps nach einer Datei "admin.py" und importiert dieses Modul automatisch.

  • Per Hand und überall mittels "admin.site.register('model', 'options')" wie im obigen Beispiel.

Ich bevorzuge den zweiten Weg, da ist weniger Voodoo im Spiel und ich kann penibel festlegen, welches App im Admin erscheint und welche Optionen ihm zugewiesen werden.

Bei meinem aktuellen Projekt habe ich dafür eine Basisklasse "DefaultModelAdmin" für alle ModelAdmins erstellt und leite davon alle anderen ModelAdmin-Klassen ab:

# File: apps/adminprefs/defaults
from django.contrib import admin

class DefaultModelAdmin(admin.ModelAdmin):
    save_on_top = True
    list_select_related = True

# File: models.py/admin.py
from django.contrib import admin
from adminprefs.defaults import DefaultModelAdmin

class EntryAdmin(DefaultModelAdmin):
    date_hierarchy = 'published'
    list_display_links = ('title')
    search_fields = ('title', 'content')

admin.site.register(Entry, EntryAdmin)

Das ist nicht nur DRY sondern wird in Zukunft auch helfen, wenn einmal ein zentraler Eingriff für alle Admin-Sachen nötig ist.

Kommentare lesen und schreiben…




ModelsForms mit Choices validiert nicht korrekt

Geschrieben 4 Wochen, 1 Tag zuvor

Um es vorweg zu nehmen, dies ist die Beschreibung eines Bugs in Django. Ja, dafür gibt es schon ein Ticket und nein, es ist derzeit noch nicht im Trunk gefixt.

Oki, wer bis hierhin durchgehalten hat, weiter gehts: Smiley:  :-)

Angenommen wir haben dieses simple Model, die Werte des Testfeldes bestehen aus den vorher definierten Choices.

from django.db import models

CHOICES = (
    (u'Bube', u'Bube'),
    (u'Dame', u'Dame'),
    (u'Koenig', u'Koenig'),
    (u'Ass', u'Ass'),
)

# Das Model
class TestModel(models.Model):
    testfeld = models.CharField(max_length=30, choices=CHOICES)

Um ein simples Formular daraus zu erstellen ist ModelForms ideal:

from testprojekt.models import TestModel
from django import forms # Django 1.0 Alpha wohooo!

class TestForm(forms.ModelForm):
    class Meta:
        model = TestModel

Einfacher gehts kaum, mit den paar Zeilen hat man schon ein komplettes Formular parat. Aber dass wußtest du sicher schon, also weiter. Basteln wir uns eine einfache Ausgabe:

>>> f = TestForm()
>>> f.as_ul()

# Beispielausgabe
<select name="testfeld" id="id_testfeld">
    <option value="Bube">Bube</option>
    <option value="Dame">Dame</option>
    <option value="Koenig">Koenig</option>
    <option value="Ass">Ass</option>
</select>

Alles korrekt bis hier hin. Angenommen das Formular wird nun auf der Webseite angezeigt, der User wählt "Bube" und schickt das Formular ab:

>>> f = TestForm(data={'testfeld': 'Bube'})
>>> f.as_ul()

<select name="testfeld" id="id_testfeld">
    <option value="Bube" selected="selected">Bube</option>
    <option value="Dame">Dame</option>
    <option value="Koenig">Koenig</option>
    <option value="Ass">Ass</option>
</select>

>>> f.is_valid()
True

Das Formular ist valide, weil "Bube" ja eine korrekte Auswahl für das Feld 'testfeld' ist. Aber zur Erinnerung: SELECT-Felder in HTML sind auch nur stinknormale Datenfelder und der Browser prüft nicht, ob der abgesendete Wert auch des Feldes auch vorher in den SELECT-Options definiert wurde. Ändern wir also mal die Werte der Formulardaten:

>>> f = TestForm(data={'testfeld': 'Boeses Maedchen'})
>>> f.as_ul()

<select name="testfeld" id="id_testfeld">
    <option value="Bube">Bube</option>
    <option value="Dame">Dame</option>
    <option value="Koenig">Koenig</option>
    <option value="Ass">Ass</option>
</select>

>>> f.is_valid()
True

Wem das nicht schlimm erscheint: Diese Daten speichert das Formular leider auch in die Datenbank, das Model selbst validiert also nicht noch einmal nach.

>>> objekt = f.save()
>>> objekt.testfeld
u'Boeses Maedchen'

Ganz fatal in meinen Augen. Zur Erinnerung, eigentlich sollte das testfeld im Model nur die Daten Bube, Dame, Koenig und Ass annehmen.

Wie kann man eigentlich "fremde" Daten in ein Select-Feld schreiben? Nichts einfacher als das, die bekannte Firefox-Extension "Web Developer Toolbar" hat eine eigene Funktion dafür:

Mein derzeitiger Lösungsvorschlag sieht so aus, dass ich einfach mit einer clean-Methode auf das Vorhandensein der Eingabe in den Choices prüfe:

from testprojekt.models import TestModel, CHOICES
from django import forms

class TestForm(forms.ModelForm):

    def clean_testfeld(self):
        if not self.cleaned_data['testfeld'] in dict(CHOICES):
            raise forms.ValidationError(u'%s is not a valid choice' % (self.cleaned_data['testfeld']))            
        return self.cleaned_data['testfeld']

    class Meta:
        model = TestModel

Dann klappts auch wie gewünscht.

>>> f = TestForm(data={'testfeld': 'Boeses Maedchen'})
>>> print f.is_valid()
False
>>> f.as_ul()
errorlist: "Boeses Maedchen is not a valid choice"
<select name="testfeld" id="id_testfeld">
    <option value="Bube">Bube</option>
    <option value="Dame">Dame</option>
    <option value="Koenig">Koenig</option>
    <option value="Ass">Ass</option>
</select

Durch das derzeitge Release von Django 1.0 Alpha gehe ich aber mal davon aus, dass dieser Bug in nächster Zeit gefixt wird. Ein Patch exisitiert ja schon.

Kommentare lesen und schreiben…




Resignation

Geschrieben 1 Monat zuvor

Ich will das aber so.

Ist der Satz von einem Kunden, ab dem man einfach resignieren muss und auch die sinnfreiesten Änderungen durchführen sollte. Mehr schreibe ich dazu nicht – zu deprimierend – nur so viel: Ein dritter "Usability-Profi" hatte seine Finger im Spiel.

Das machen aber alle so.

Kommentare lesen und schreiben…




Gib mir Pagerank

Geschrieben 1 Monat, 2 Wochen zuvor

Es ist weder Montag noch Freitag. Warum werde ich dann heute mit solchen Kundenanfragen gestraft?

warum haben wir auf unserer hp kein pakerank ??
wenn moeglich bitte schnellstens einrichten !

Das ist Pagerank.

Kommentare lesen und schreiben…




Wie bei newforms die Validierung abläuft

Geschrieben 1 Monat, 3 Wochen zuvor

Djangos newforms Formularen stehen 3 Methoden zur Verfügung, Eingabedaten zu prüfen.

  • Feld.clean(): Die clean-Methode des Feldes selbst. Django bringt für die jeweiligen Felder schon grundsätzliche Prüfungen mit. In ein IntegerField dürfen eben nur Ziffern eingegeben werden und ein EmailField prüft die korrekte Syntax der E-Mail-Adresse.

In der Regel ist es nicht notwendig, bestehende Feld-Clean-Methoden zu ändern, denn dafür gibt es:

  • Formular.clean_<feldname>(): In dieser Funktion kannst du penibel festlegen, welche Daten für das Feld valide sind und einen Fehler ausgeben oder diese Daten ändern.

  • Formular.clean(): Diese Funktion wird am Ende ausgeführt, wenn alle Felder einzeln schon geprüft worden sind.

In allen Funktionen hat das Formular stets Zugriff auf die eingegeben Daten, denn diese stehen im Dict self.cleaned_data. Wofür ist also die "globale" clean() Methode gut?

Dazu ist es wichtig zu wissen, wie der Ablauf der Validierung von Newforms-Formularen stattfindet. Vergleiche einmal dieses Formular und dessen Ausgabe:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from django import newforms as forms

class Form(forms.Form):
    name = forms.CharField()
    plz = forms.IntegerField()
    ort = forms.CharField()

    def clean_name(self):
        print "Vorhandene Daten bei 'name': %s" % self.cleaned_data
        return self.cleaned_data['name']

    def clean_plz(self):
        print "Vorhandene Daten bei 'plz': %s" % self.cleaned_data
        return self.cleaned_data['plz']

    def clean_ort(self):
        print "Vorhandene Daten bei 'ort': %s" % self.cleaned_data
        return self.cleaned_data['ort']

    def clean(self):
        print "Die Formular clean() Methode. Spätestens jetzt sind alle Daten" \
              "vorhanden: %s" % self.cleaned_data
        return self.cleaned_data

test_data = {'name': 'Martin', 'plz': '18581', 'ort': 'Putbus'}
f = Form(test_data)
f.as_ul()

Ausgabe:

>python -u test.py
Vorhandene Daten bei 'name': {'name': u'Martin'}
Vorhandene Daten bei 'plz': {'name': u'Martin', 'plz': 18581}
Vorhandene Daten bei 'ort': {'ort': u'Putbus', 'name': u'Martin', 'plz': 18581}
Die Formular clean() Methode. Spätestens jetzt
sind alle Datenvorhanden: {'ort': u'Putbus', 'name': u'Martin', 'plz': 18581}

Zur Erläuterung. Die Validierung im Formular geht der Reihe nach, anhand der Felder von oben nach unten. In diesem Formular ist es also dieses Schema:

  1. 'name' den Wert aus den Daten zuweisen
  2. name.clean() - Die interne clean-Funktion eines CharFields
  3. f.clean_name() - Unsere selbst definierte clean-Funktion für 'name'
  4. 'plz' den Wert aus den Daten zuweisen
  5. plz.clean() - Die interne clean-Funktion eines IntegerFields
  6. f.clean_plz() - Unsere selbst definierte clean-Funktion für 'plz'
  7. 'ort' den Wert aus den Daten zuweisen
  8. ort.clean() - Die interne clean-Funktion eines CharFields
  9. f.clean_ort() - Unsere selbst definierte clean-Funktion für 'ort'
  10. f.clean() - Die "globale" Clean-Methode wird erst am Ende ausgeführt.

In der Ausgabe siehst du auch, das dir in der Funktion "clean_name" (unser erstes Feld) noch gar keine Werte für die Felder "plz" und "ort" zur Verfügung stehen. Nochmal: die Validierung und Zuweisung der Daten läuft von oben nach unten, in der Reihenfolge der Felder.

Warum kann man also schon im ersten Feld nicht auf alle Eingabedaten zugreifen? Hypothese: Wozu soll das Formular auch alle Datenfelder durchlaufen, wenn schon im ersten Feld 'name' ein Fehler (ValidationError) auftreten kann.

Aha, toll

Ok, kommen wir endlich zum springenden Punkt für diesen Artikel. In der Regel wird ein Formular nicht nur pro Feld validiert sondern auch in Kombination. Beispiele währen:

  • 2 Passwort-Felder müssen das selbe Passwort haben
  • Möchte ich per Lastschrift zahlen, muss ich auch Kontodaten hinterlegen
  • Wünsche ich Antwort per E-Mail, muss ich auch eine E-Mail-Adresse hinterlegen, sonst reicht mein Name

Und so weiter, das ganze hängt ja vom Anwendungsfall ab und die Möglichkeiten so eines Formulares sind unbegrenzt. Nun endlich der springende Punkt, und ich würde das ganze hier nicht schreiben, wenn es nicht ein häufiges (Anfänger)Problem währe:

Willst du mehrere Felder miteinander vergleichen gehört diese Logik in die clean()-Funktion des Formulares, nicht in die des Feldes! Der Grund dafür steht ja schon oben.

Nun aber das Aber. Der Grund warum so viele Leute die Validierungslogik in eine clean_<feldname> Funktion packen – obwohl es mehrere Felder betrifft – ist, dass Fehlermeldungen aus der clean() Funktion immer ganz oben, vor allen Feldern angezeigt werden. Die so genannten non_field_errors, also Fehlermeldungen die nicht an ein Feld gebunden sind.

Hier ein Beispiel dass die Felder 'ort' und 'name' miteinander prüft:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from django import newforms as forms

class Form(forms.Form):
    name = forms.CharField()
    plz = forms.IntegerField()
    ort = forms.CharField()

    def clean(self):
        if self.cleaned_data['name'] == 'Meier' and \
           self.cleaned_data['ort'] == 'Berlin':
            raise forms.ValidationError('Es gibt keinen "Meier" in Berlin.' \
                  'Bitte legen Sie sich einen anderen Namen zu.')

test_data = {'name': 'Meier', 'plz': u'10627', 'ort': 'Berlin'}
f = Form(test_data)
print f.as_ul()

In der Ausgabe des Formulares steht der Fehler ganz oben:

<!--
    non_field_erros Fehlermeldung
    (normalerweise ganz oben)
-->
<li><ul class="errorlist"><li>Es gibt keinen "Meier" in Berlin.Bitte legen Sie sich einen anderen Namen zu.</li></ul></li>

<!--
    Weiter gehts mit den Feldern, die aber
    keine eigenen Fehlermeldungen besitzen
-->
<li><label for="id_name">Name:</label> <input type="text" name="name" value="Meier" id="id_name" /></li>
<li><label for="id_plz">Plz:</label> <input type="text" name="plz" value="10627" id="id_plz" /></li>
<li><label for="id_ort">Ort:</label> <input type="text" name="ort" value="Berlin" id="id_ort" /></li>

Möchte man aber im Nachhinein noch einem Feld einen Fehlertext zuweisen, so geht das einfach über das Dict self.errors[<feldname>] (es erwartet für das Feld ein Liste oder Tupel, über die es iterieren kann):

#!/usr/bin/env python
class Form(forms.Form):
    # ...
    def clean(self):
        if self.cleaned_data['name'] == 'Meier' and \
           self.cleaned_data['ort'] == 'Berlin':
            self.errors['name'] = ('Es gibt keinen "Meier" in Berlin.',)
            raise forms.ValidationError('Es sind Fehler aufgetreten:')

In der Ausgabe hat unser Feld nun seine eigene Fehlermeldung:

<!--
    non_field_erros Fehlermeldung
    (normalerweise ganz oben)
-->
<li><ul class="errorlist"><li>Es sind Fehler aufgetreten:</li></ul></li>

<!--
    Unser Namensfeld mit einer eigenen Fehlermeldung
-->
<li>
    <ul class="errorlist"><li>Es gibt keinen &quot;Meier&quot; in Berlin.</li></ul>
    <label for="id_name">Name:</label> <input type="text" name="name" value="Meier" id="id_name" />
</li>
<li><label for="id_plz">Plz:</label> <input type="text" name="plz" value="10627" id="id_plz" /></li>
<li><label for="id_ort">Ort:</label> <input type="text" name="ort" value="Berlin" id="id_ort" /></li>

So weit... viel Spass weiterhin mit newforms. Smiley:  :-)

Kommentare lesen und schreiben…




Python difflib Praxisbeispiel

Geschrieben 2 Monate zuvor

Für eine Communityseite möchte ich, wenn ein Beitrag geändert wird, dem Vorautor eine E-Mail mit den Änderungen schicken. Die klassische Ausgabe eines Diffs gefällt mir besonders gut. Hier ist ein kleines Beispiel wie das Pythonmodul difflib zu gebrauchen ist:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import difflib

a = """
Lorem ipsum dolor sit amen, consectetuer adipiscing elit. Nunc ac ante sed
ante imperdiet auctor. Fusce dignissim, magna eu feugiat tincidunt, nibh metus
tincidunt augue, quis ullamcorper lorem pede a ante.
"""

b = """
Lorem ipsum dolor sit amen, consectetuer adipiscing eliot. Nunc ac ante sed
ante imperdiet auctor. Fusce dignissim, magna eu feugiat tincidunt, nibh metus
tincidunt augue, quis ullamcorper lorem pede a onte!
"""

d = difflib.unified_diff(
    a.splitlines(),
    b.splitlines(), 
    'Original',
    'Current',
    lineterm=''
)

print '\n'.join(d)

Und die Ausgabe:

--- Original 
+++ Current 
@@ -1,4 +1,4 @@

-Lorem ipsum dolor sit amen, consectetuer adipiscing elit. Nunc ac ante sed
+Lorem ipsum dolor sit amen, consectetuer adipiscing eliot. Nunc ac ante sed
 ante imperdiet auctor. Fusce dignissim, magna eu feugiat tincidunt, nibh metus
-tincidunt augue, quis ullamcorper lorem pede a ante.
+tincidunt augue, quis ullamcorper lorem pede a onte!

Wer eher nach einer farbigen, detailierten Ausgabe wie bei Trac sucht (hier im Beispiel übernimmt Pygments im Nachhinein das Highlighting), sollte sich auch das HtmlDiff Modul anschauen.

Kommentare lesen und schreiben…


Seite: 123Weiter >Letzte »


Du bist hier: mahner.org » Weblog

↑ Nach oben

© 2001–2007 Martin Mahner. The content is licensed under (cc) creative commons.
This site is powered by Django. Hosted by manitu.

Generated: Do, 21 Aug 2008 08:24:52 +0200